diff --git a/src/main/java/network/brightspots/rcv/Tabulator.java b/src/main/java/network/brightspots/rcv/Tabulator.java index d4e6bea86..8a7f4159d 100644 --- a/src/main/java/network/brightspots/rcv/Tabulator.java +++ b/src/main/java/network/brightspots/rcv/Tabulator.java @@ -493,19 +493,30 @@ private List identifyWinners( } else { // We should only look for more winners if we haven't already filled all the seats. if (winnerToRound.size() < config.getNumberOfWinners()) { - // If the number of continuing candidates equals the number of seats to fill, everyone wins. if (currentRoundCandidateToTally.size() == config.getNumberOfWinners() - winnerToRound.size()) { + // If the number of continuing candidates equals the number of seats to fill, everyone wins. selectedWinners.addAll(currentRoundCandidateToTally.keySet()); + } else if (config.isFirstRoundDeterminesThresholdEnabled() && + currentRoundCandidateToTally.size() - 1 == config.getNumberOfWinners()) { + // Edge case: if nobody meets the threshold, but we're on the penultimate round when + // isFirstRoundDeterminesThresholdEnabled is true, select the max vote getters as the winners. + BigDecimal maxVotes = currentRoundTallyToCandidates.lastKey(); + selectedWinners = currentRoundTallyToCandidates.get(maxVotes); } else if (!config.isMultiSeatBottomsUpUntilNWinnersEnabled()) { + // Otherwise, select all winners above the threshold selectWinners(currentRoundTallyToCandidates, selectedWinners); } } // Edge case: if we've identified multiple winners in this round, but we're only supposed to - // elect one winner per round, pick the top vote-getter and defer the others to subsequent - // rounds. - if (config.isMultiSeatAllowOnlyOneWinnerPerRoundEnabled() && selectedWinners.size() > 1) { + // elect one winner per round, pick the top vote-getter. If this is a multi-winner election, + // defer the others to subsequent rounds. If this is a single-winner election where its possible + // for no candidate to reach the threshold (isFirstRoundDeterminesThresholdEnabled=true), the tiebreaker + // will choose the only winner. + boolean useTiebreakerIfNeeded = config.isMultiSeatAllowOnlyOneWinnerPerRoundEnabled() + || config.isFirstRoundDeterminesThresholdEnabled(); + if (useTiebreakerIfNeeded && selectedWinners.size() > 1) { // currentRoundTallyToCandidates is sorted from low to high, so just look at the last key BigDecimal maxVotes = currentRoundTallyToCandidates.lastKey(); selectedWinners = currentRoundTallyToCandidates.get(maxVotes); diff --git a/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_test/first_round_determines_threshold_test_expected_summary.json b/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_test/first_round_determines_threshold_test_expected_summary.json index 8e69d9933..04b64cd2f 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_test/first_round_determines_threshold_test_expected_summary.json +++ b/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_test/first_round_determines_threshold_test_expected_summary.json @@ -25,17 +25,6 @@ "Carmona, Ralph C." : "5", "Vail, Christopher L." : "6" }, - "tallyResults" : [ { - "eliminated" : "Carmona, Ralph C.", - "transfers" : { - "exhausted" : "5" - } - } ] - }, { - "round" : 3, - "tally" : { - "Vail, Christopher L." : "6" - }, "tallyResults" : [ { "elected" : "Vail, Christopher L.", "transfers" : { } diff --git a/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_tiebreaker_test/first_round_determines_threshold_tiebreaker_test_expected_summary.json b/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_tiebreaker_test/first_round_determines_threshold_tiebreaker_test_expected_summary.json index 162aa0209..42ca9a7ce 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_tiebreaker_test/first_round_determines_threshold_tiebreaker_test_expected_summary.json +++ b/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_tiebreaker_test/first_round_determines_threshold_tiebreaker_test_expected_summary.json @@ -26,18 +26,7 @@ "Vail, Christopher L." : "6" }, "tallyResults" : [ { - "eliminated" : "Vail, Christopher L.", - "transfers" : { - "exhausted" : "6" - } - } ] - }, { - "round" : 3, - "tally" : { - "Carmona, Ralph C." : "6" - }, - "tallyResults" : [ { - "elected" : "Carmona, Ralph C.", + "elected" : "Vail, Christopher L.", "transfers" : { } } ] } ]