determine candidates who may be bulk excluded, under 273(13)

def determine_bulk_exclusions(self, candidate_aggregates):
        "determine candidates who may be bulk excluded, under 273(13)"
        # adjustment as under (13C) - seems to only apply if more than one candidate was elected in a round
        continuing = self.get_continuing_candidates(candidate_aggregates)
        candidate_votes = candidate_aggregates.get_candidate_votes()
        by_votes = self.get_votes_to_candidates(continuing, candidate_aggregates)
        adjustment = sum(excess_votes for _, _, excess_votes in self.election_distributions_pending)
        candidate_notional_votes = self.get_candidate_notional_votes(candidate_aggregates, adjustment)
        leading_shortfall, vacancy_shortfall = self.get_leading_and_vacancy_shortfall(candidate_aggregates)

        def determine_candidate_A():
            # notional votes >= vacancy shortfall
            eligible = [candidate_id for candidate_id, notional in candidate_notional_votes.items() if notional >= vacancy_shortfall]
            if len(eligible) == 0:
                return None
            # lowest in the poll: tie is irrelevant
            eligible.sort(key=lambda candidate_id: candidate_votes[candidate_id])
            return eligible[0]

        sorted_votes = list(sorted(by_votes.keys()))

        def notional_lower_than_higher(candidate_id):
            "check notional votes of candidate is lower than the number of votes of the candidate standing immediately higher"
            votes = candidate_votes[candidate_id]
            votes_idx = sorted_votes.index(votes)
            # no higher candidate
            if votes_idx == len(sorted_votes) - 1:
                return False
            # legislation ambiguous, but if there's a tie above us let's check all the candidates on that count
            notional = candidate_notional_votes[candidate_id]
            higher_votes = sorted_votes[votes_idx + 1]
            acceptable = all(notional < candidate_votes[t] for t in by_votes[higher_votes])
            return acceptable

        def highest(eligible):
            "return the highest ranked candidate in candidates, by vote. if a tie, or list empty, return None"
            if not eligible:
                return
            binned = self.get_votes_to_candidates(eligible, candidate_aggregates)
            possible = binned[max(binned)]
            if len(possible) == 1:
                return possible[0]

        def determine_candidate_B(candidate_A):
            if candidate_A is not None:
                A_votes = candidate_votes[candidate_A]
                eligible = [candidate_id for candidate_id in continuing if candidate_votes[candidate_id] < A_votes]
            else:
                eligible = [candidate_id for candidate_id in continuing if candidate_notional_votes[candidate_id] < vacancy_shortfall]
            eligible = [candidate_id for candidate_id in eligible if notional_lower_than_higher(candidate_id)]
            return highest(eligible)

        def determine_candidate_C():
            eligible = [candidate_id for candidate_id in continuing if candidate_notional_votes[candidate_id] < leading_shortfall]
            # the candidate of those eligible which stands highest in the poll
            eligible.sort(key=lambda candidate_id: candidate_votes[candidate_id])
            return highest(eligible)

        def candidates_lte(candidate_id):
            votes = candidate_votes[candidate_id]
            lower_votes = [t for t in by_votes.keys() if t < votes]
            to_exclude = [candidate_id]
            for vote in lower_votes:
                to_exclude += [candidate_id for candidate_id in by_votes[vote] if candidate_id in continuing]
            return to_exclude

        # candidate A, B, C as under (13A)(a)
        to_exclude = None
        candidate_A = determine_candidate_A()
        candidate_B = determine_candidate_B(candidate_A)
        candidate_C = None
        if candidate_B:
            candidate_B_votes = candidate_votes[candidate_B]
            if candidate_B_votes < leading_shortfall:
                to_exclude = candidates_lte(candidate_B)
            else:
                candidate_C = determine_candidate_C()
                if candidate_C:
                    to_exclude = candidates_lte(candidate_C)
        if to_exclude and len(to_exclude) == 1:
            to_exclude = None
        return to_exclude, ExclusionReason("bulk", {
            "candidate_A": candidate_A,
            "candidate_B": candidate_B,
            "candidate_C": candidate_C
        })