def test_multi_vacancy_vote_transfer_scenario_enough(app): c = [ Candidate("C0"), Candidate("C1"), Candidate("C2"), Candidate("C3"), ] ballots = [ Ballot(preference=[c[0], c[2]]), Ballot(preference=[c[0], c[2]]), Ballot(preference=[c[0], c[2]]), Ballot(preference=[c[0], c[2]]), Ballot(preference=[c[0], c[2]]), Ballot(preference=[c[0], c[3]]), Ballot(preference=[c[1]]), Ballot(preference=[c[1]]), Ballot(preference=[c[1]]), Ballot(preference=[c[2]]), ] tally = Tally(ballots=ballots, vacancies=2) elected, discarded, rejected = tally.run() assert set(elected) == {c[0], c[2]} assert set(discarded) == {c[1], c[3]} assert set(rejected) == set()
def test_arbitrary_decision_provided(app): c = [ Candidate("C0"), Candidate("C1"), Candidate("C2"), Candidate("C3"), ] ballots = [ Ballot(preference=[c[0]]), Ballot(preference=[c[1]]), Ballot(preference=[c[2]]), Ballot(preference=[c[3]]), Ballot(preference=[c[3]]), ] arbitrary_discards = [ [c[0], c[1]], ] tally = Tally(ballots=ballots, arbitrary_discards=arbitrary_discards) elected, discarded, rejected = tally.run() assert set(elected) == {c[3]} assert set(discarded) == {c[0], c[1], c[2]} assert set(rejected) == set()
def test_ballot_active(app): c = [ Candidate("C0"), Candidate("C1"), ] ballot = Ballot(preference=[c[0], c[1]]) ballot.check_active() assert ballot.value == 1
def test_ballot_preference_nontrivial_duplicate(app): c = [ Candidate("C0"), Candidate("C0"), ] with pytest.raises(ValueError): Ballot(preference=[c[0], c[1]])
def test_tally_with_mutual_rejection_paradox(app, feature_flags): feature_flags.enable(FEATURE_STV_REJECTION) c = [ Candidate("C0"), Candidate("C1"), Candidate("C2"), ] ballots = [ Ballot(preference=[c[0]], reject=[c[1]]), Ballot(preference=[c[0]], reject=[c[1]]), Ballot(preference=[c[0]], reject=[c[1]]), Ballot(preference=[c[1]], reject=[c[0]]), Ballot(preference=[c[1]], reject=[c[0]]), Ballot(preference=[c[1]], reject=[c[0]]), Ballot(preference=[c[0], c[1], c[2]]), ] tally = Tally(ballots=ballots) elected, discarded, rejected = tally.run() assert set(elected) == {c[2]} assert set(discarded) == set() assert set(rejected) == {c[0], c[1]}
def test_tally_simple_run_001(app): c = [ Candidate("C0"), Candidate("C1"), Candidate("C2"), ] ballots = [ Ballot(preference=[c[0], c[2]]), Ballot(preference=[c[0], c[1]]), Ballot(preference=[c[2], c[1]]), ] tally = Tally(ballots=ballots) elected, discarded, rejected = tally.run() assert set(elected) == {c[0]} assert set(discarded) == {c[1], c[2]} assert set(rejected) == set()
def test_arbitrary_decision_needed(app): c = [ Candidate("C0"), Candidate("C1"), Candidate("C2"), Candidate("C3"), ] ballots = [ Ballot(preference=[c[0]]), Ballot(preference=[c[1]]), Ballot(preference=[c[2]]), Ballot(preference=[c[3]]), Ballot(preference=[c[3]]), ] tally = Tally(ballots=ballots) with raises(ArbitraryDiscardDecisionNeededError): tally.run()
def test_ballot_preference_reject_conflict(app, feature_flags): feature_flags.enable(FEATURE_STV_REJECTION) c = [ Candidate("C0"), Candidate("C1"), Candidate("C2"), ] with pytest.raises(ValueError): Ballot(preference=[c[0], c[1]], reject=[c[0], c[2]])
def test_single_candidate_with_zero_score_3(app): c = [ Candidate("C0"), Candidate("C1"), ] ballots = [ Ballot(preference=[c[1]]), ] tally = Tally(ballots=ballots, candidates=c, vacancies=2) elected, discarded, rejected = tally.run() assert set(elected) == {c[1]} assert set(discarded) == {c[0]} assert set(rejected) == set()
def test_multi_vacancy_tally_with_single_ballot(app): c = [ Candidate("C0"), Candidate("C1"), Candidate("C2"), Candidate("C3"), Candidate("C4"), ] ballots = [ Ballot(preference=[c[3], c[1], c[4], c[2], c[0]]), ] tally = Tally(ballots=ballots, vacancies=3) elected, discarded, rejected = tally.run() assert set(elected) == {c[1], c[3], c[4]} assert set(discarded) == {c[0], c[2]} assert set(rejected) == set()
def get_position_scores(position): candidates = [ Candidate(id=candidate.id) for candidate in position.candidates ] arbitrary_discards = [] for discard in position.arbitrary_discards: candidates_to_discard = [ Candidate(id=candidate.id) for candidate in discard.discarded ] arbitrary_discards.append(candidates_to_discard) ballots = set() for vote in position.votes.all(): preference = [Candidate(id=kmsid) for kmsid in vote.preference] reject = {Candidate(id=kmsid) for kmsid in vote.reject} ballots.add(Ballot(preference, reject)) vacancies = 1 if position.name == POSITION_BOSS else 3 return Tally(ballots, vacancies, candidates=candidates, arbitrary_discards=arbitrary_discards).count_votes()
def test_ballot_inactive(app): ballot = Ballot(preference=[]) ballot.check_active() assert ballot.value == 0
def reckon_position(position, reckon_again=False): log.info(f'Reckoning position {position}') if not position.is_reckoned or reckon_again: log.info(f'Position not yet reckoned {position}') candidates = [ Candidate(id=candidate.id) for candidate in position.candidates ] arbitrary_discards = [] for discard in position.arbitrary_discards: candidates_to_discard = [ Candidate(id=candidate.id) for candidate in discard.discarded ] arbitrary_discards.append(candidates_to_discard) log.debug(f'candidates = {candidates}') ballots = set() for vote in position.votes.all(): log.debug(f'Processing vote {vote}') log.debug(f'vote.preference = {vote.preference}') log.debug(f'vote.reject = {vote.reject}') preference = [Candidate(id=kmsid) for kmsid in vote.preference] reject = {Candidate(id=kmsid) for kmsid in vote.reject} ballots.add(Ballot(preference, reject)) vacancies = 1 if position.name == POSITION_BOSS else 3 try: elected_candidates, discarded_candidates, rejected_candidates = Tally( ballots, vacancies, candidates=candidates, arbitrary_discards=arbitrary_discards).run() except ArbitraryDiscardDecisionNeededError as e: e.position = position for candidate in e.candidates: candidate.fellow = Fellow.query.filter_by( id=candidate.id).first() raise e elected_fellows = [ Fellow.query.filter_by(id=candidate.id).first() for candidate in elected_candidates ] discarded_fellows = [ Fellow.query.filter_by(id=candidate.id).first() for candidate in discarded_candidates ] rejected_fellows = [ Fellow.query.filter_by(id=candidate.id).first() for candidate in rejected_candidates ] if position.name == POSITION_FREE: election = position.election for p in election.positions.all(): if p.name not in (POSITION_BOSS, POSITION_FREE, POSITION_COVISION): elected, _, _ = reckon_position(p) elected_fellows.extend(elected) position.elected = elected_fellows position.discarded = discarded_fellows position.rejected = rejected_fellows position.is_reckoned = True db.session.commit() elected = position.elected.order_by(Fellow.surname.asc(), Fellow.name.asc()).all() discarded = position.discarded.order_by(Fellow.surname.asc(), Fellow.name.asc()).all() rejected = position.rejected.order_by(Fellow.surname.asc(), Fellow.name.asc()).all() return elected, discarded, rejected