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_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_ballot_preference_nontrivial_duplicate(app):
    c = [
        Candidate("C0"),
        Candidate("C0"),
    ]

    with pytest.raises(ValueError):
        Ballot(preference=[c[0], c[1]])
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_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_tally_with_single_ballot(app):
    c = [
        Candidate("C0"),
        Candidate("C1"),
        Candidate("C2"),
    ]

    ballots = [
        Ballot(preference=[c[0], c[1], c[2]]),
    ]

    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()
Beispiel #10
0
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()
Beispiel #11
0
def test_tally_simple_run_002(app):
    c = [
        Candidate("C0"),
        Candidate("C1"),
        Candidate("C2"),
    ]

    ballots = [
        Ballot(preference=[c[0]]),
        Ballot(preference=[c[0]]),
        Ballot(preference=[c[1]]),
    ]

    tally = Tally(ballots=ballots)

    elected, discarded, rejected = tally.run()

    assert set(elected) == {c[0]}
    assert set(discarded) == {c[1]}
    assert set(rejected) == set()
Beispiel #12
0
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()
Beispiel #13
0
def test_single_candidate_with_zero_score(app):
    c = [
        Candidate("C0"),
    ]

    ballots = []

    tally = Tally(ballots=ballots, candidates=c)

    elected, discarded, rejected = tally.run()

    assert set(elected) == set()
    assert set(discarded) == {c[0]}
    assert set(rejected) == set()
Beispiel #14
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