diff --git a/src/main/java/network/brightspots/rcv/CastVoteRecord.java b/src/main/java/network/brightspots/rcv/CastVoteRecord.java index f34e9c43..30dbed6c 100644 --- a/src/main/java/network/brightspots/rcv/CastVoteRecord.java +++ b/src/main/java/network/brightspots/rcv/CastVoteRecord.java @@ -235,12 +235,67 @@ Map getWinnerToFractionalValue() { // as far as tabulation is concerned, all that matters is whether // it is active or not. enum StatusForRound { - ACTIVE, - DID_NOT_RANK_ANY_CANDIDATES, - EXHAUSTED_CHOICE, - INVALIDATED_BY_OVERVOTE, - INVALIDATED_BY_SKIPPED_RANKING, - INVALIDATED_BY_REPEATED_RANKING, + ACTIVE( + false, + "Active", + "active" + ), + DID_NOT_RANK_ANY_CANDIDATES( + true, + "Did Not Rank Any Candidates", + "didNotRankAnyCandidates" + ), + EXHAUSTED_CHOICE( + true, + "Inactive Ballots by Exhausted Choices", + "exhaustedChoices" + ), + INVALIDATED_BY_OVERVOTE( + true, + "Inactive Ballots by Overvotes", + "overvotes" + ), + INVALIDATED_BY_SKIPPED_RANKING( + true, + "Inactive Ballots by Skipped Rankings", + "skippedRankings" + ), + INVALIDATED_BY_REPEATED_RANKING( + true, + "Inactive Ballots by Repeated Rankings", + "repeatedRankings" + ), + FINAL_ROUND_SURPLUS( + false, + "Final Round Surplus", + "finalRoundSurplus" + ); + + private final boolean isInactiveBallot; + private final String titleCaseKey; + private final String camelCaseKey; + + StatusForRound( + boolean isInactiveBallot, + String titleCaseKey, + String camelCaseKey + ) { + this.isInactiveBallot = isInactiveBallot; + this.titleCaseKey = titleCaseKey; + this.camelCaseKey = camelCaseKey; + } + + public boolean isInactiveBallot() { + return isInactiveBallot; + } + + public String getTitleCaseKey() { + return titleCaseKey; + } + + public String getCamelCaseKey() { + return camelCaseKey; + } } enum VoteOutcomeType { diff --git a/src/main/java/network/brightspots/rcv/ContestConfig.java b/src/main/java/network/brightspots/rcv/ContestConfig.java index d32bdcc8..d261eaff 100644 --- a/src/main/java/network/brightspots/rcv/ContestConfig.java +++ b/src/main/java/network/brightspots/rcv/ContestConfig.java @@ -947,6 +947,10 @@ boolean isNonIntegerWinningThresholdEnabled() { return rawConfig.rules.nonIntegerWinningThreshold; } + boolean usesSurpluses() { + return getNumberOfWinners() > 1 && !isMultiSeatBottomsUpUntilNWinnersEnabled(); + } + boolean isHareQuotaEnabled() { return rawConfig.rules.hareQuota; } diff --git a/src/main/java/network/brightspots/rcv/ResultsWriter.java b/src/main/java/network/brightspots/rcv/ResultsWriter.java index 039681f7..d410e958 100644 --- a/src/main/java/network/brightspots/rcv/ResultsWriter.java +++ b/src/main/java/network/brightspots/rcv/ResultsWriter.java @@ -46,6 +46,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.stream.Collectors; import javafx.util.Pair; import network.brightspots.rcv.ContestConfig.TabulateBySlice; import network.brightspots.rcv.RawContestConfig.CvrSource; @@ -78,6 +79,14 @@ class ResultsWriter { private String timestampString; // map from round number to residual surplus generated in that round private Map roundToResidualSurplus; + // statuses to print in all summary files + // (additional fields are added if needed in specific summary filetypes) + private static final List STATUSES_TO_PRINT = List.of( + StatusForRound.INVALIDATED_BY_OVERVOTE, + StatusForRound.INVALIDATED_BY_SKIPPED_RANKING, + StatusForRound.EXHAUSTED_CHOICE, + StatusForRound.INVALIDATED_BY_REPEATED_RANKING); + // visible for testing @SuppressWarnings("WeakerAccess") @@ -382,20 +391,9 @@ private void generateSummarySpreadsheet( csvPrinter.println(); } - List> statusesToPrint = new ArrayList<>(); - statusesToPrint.add(new Pair<>("Overvotes", - StatusForRound.INVALIDATED_BY_OVERVOTE)); - statusesToPrint.add(new Pair<>("Skipped Rankings", - StatusForRound.INVALIDATED_BY_SKIPPED_RANKING)); - statusesToPrint.add(new Pair<>("Exhausted Choices", - StatusForRound.EXHAUSTED_CHOICE)); - statusesToPrint.add(new Pair<>("Repeated Rankings", - StatusForRound.INVALIDATED_BY_REPEATED_RANKING)); - - for (Pair statusToPrint : statusesToPrint) { - csvPrinter.print("Inactive Ballots by " + statusToPrint.getKey()); + for (StatusForRound status : STATUSES_TO_PRINT) { + csvPrinter.print(status.getTitleCaseKey()); - StatusForRound status = statusToPrint.getValue(); for (int round = 1; round <= numRounds; round++) { BigDecimal thisRoundInactive = roundTallies.get(round).getBallotStatusTally(status); csvPrinter.print(thisRoundInactive); @@ -455,6 +453,21 @@ private void generateSummarySpreadsheet( csvPrinter.println(); } + if (config.usesSurpluses()) { + // row for final round surplus (if needed) + csvPrinter.print(StatusForRound.FINAL_ROUND_SURPLUS.getTitleCaseKey()); + for (int round = 1; round <= numRounds; round++) { + BigDecimal finalRoundSurplus = + roundTallies.get(round).getBallotStatusTally(StatusForRound.FINAL_ROUND_SURPLUS); + csvPrinter.print(finalRoundSurplus.equals(BigDecimal.ZERO) ? "" : finalRoundSurplus); + + // Don't display transfer or percentage of residual surplus + csvPrinter.print(""); + csvPrinter.print(""); + } + csvPrinter.println(); + } + if (isSlice) { csvPrinter.println(); csvPrinter.print(String.format("*Elect/Eliminate decisions are from the full contest. " @@ -1012,23 +1025,16 @@ private Map updateCandidateNamesInTally(RoundTally roundSumm } private Map getInactiveJsonMap(RoundTally roundTally) { - Map inactiveMap = new HashMap<>(); - Pair[] statusesToPrint = - new Pair[] { - new Pair<>("overvotes", - StatusForRound.INVALIDATED_BY_OVERVOTE), - new Pair<>("skippedRankings", - StatusForRound.INVALIDATED_BY_SKIPPED_RANKING), - new Pair<>("repeatedRankings", - StatusForRound.INVALIDATED_BY_REPEATED_RANKING), - new Pair<>("exhaustedChoices", - StatusForRound.EXHAUSTED_CHOICE), - }; - for (Pair statusToPrint : statusesToPrint) { - inactiveMap.put( - statusToPrint.getKey(), roundTally.getBallotStatusTally(statusToPrint.getValue())); + Map result = STATUSES_TO_PRINT.stream() + .collect(Collectors.toMap(StatusForRound::getCamelCaseKey, + roundTally::getBallotStatusTally)); + + if (config.usesSurpluses() && roundTally.getRoundNumber() == numRounds) { + result.put(StatusForRound.FINAL_ROUND_SURPLUS.getCamelCaseKey(), + roundTally.getBallotStatusTally(StatusForRound.FINAL_ROUND_SURPLUS)); } - return inactiveMap; + + return result; } // adds action objects to input action list representing all actions applied this round @@ -1047,7 +1053,7 @@ private void addActionObjects( TallyTransfers tallyTransfers) { // check for valid candidates: // "drop undeclared write-in" may result in no one actually being eliminated - if (candidates != null && candidates.size() > 0) { + if (candidates != null && !candidates.isEmpty()) { // transfers contains all vote transfers for this round // we add one to the round since transfers are currently stored under the round AFTER // the tallies which triggered them diff --git a/src/main/java/network/brightspots/rcv/RoundTally.java b/src/main/java/network/brightspots/rcv/RoundTally.java index 2ff4cf8c..98c03860 100644 --- a/src/main/java/network/brightspots/rcv/RoundTally.java +++ b/src/main/java/network/brightspots/rcv/RoundTally.java @@ -204,14 +204,15 @@ public List getSortedCandidatesByTally() { private void countBallots() { inactiveBallotSum = BigDecimal.ZERO; + activeBallotSum = BigDecimal.ZERO; ballotStatusTallies.forEach( (statusForRound, tally) -> { - if (statusForRound != StatusForRound.ACTIVE) { + if (statusForRound.isInactiveBallot()) { inactiveBallotSum = inactiveBallotSum.add(tally); + } else { + activeBallotSum = activeBallotSum.add(tally); } }); - - activeBallotSum = ballotStatusTallies.get(StatusForRound.ACTIVE); } private void ensureFinalized() { diff --git a/src/main/java/network/brightspots/rcv/Tabulator.java b/src/main/java/network/brightspots/rcv/Tabulator.java index de52e372..0f44f583 100644 --- a/src/main/java/network/brightspots/rcv/Tabulator.java +++ b/src/main/java/network/brightspots/rcv/Tabulator.java @@ -206,7 +206,7 @@ Set tabulate(Progress progress) throws TabulationAbortedException { } // In multi-seat contests, we always redistribute the surplus (if any) unless bottoms-up // is enabled. - if (config.getNumberOfWinners() > 1 && !config.isMultiSeatBottomsUpUntilNWinnersEnabled()) { + if (config.usesSurpluses()) { for (String winner : winners) { BigDecimal candidateVotes = currentRoundTally.getCandidateTally(winner); // number that were surplus (beyond the required threshold) @@ -502,9 +502,8 @@ private boolean shouldContinueTabulating() { // bottoms-up is enabled, in which case we can stop as soon as we've declared the winners. keepTabulating = numWinnersDeclared < config.getNumberOfWinners() - || (config.getNumberOfWinners() > 1 - && winnerToRound.containsValue(currentRound) - && !config.isMultiSeatBottomsUpUntilNWinnersEnabled()); + || (config.usesSurpluses() + && winnerToRound.containsValue(currentRound)); } return keepTabulating; } @@ -1006,24 +1005,10 @@ private void recordSelectionForCastVoteRecord( } } - String outcomeDescription; - switch (statusForRound) { - case ACTIVE -> outcomeDescription = selectedCandidate; - case DID_NOT_RANK_ANY_CANDIDATES -> outcomeDescription = - "did not rank any candidates" + additionalLogText; - case INVALIDATED_BY_OVERVOTE -> outcomeDescription = - "invalidated by overvote" + additionalLogText; - case EXHAUSTED_CHOICE -> outcomeDescription = - "exhausted choice" + additionalLogText; - case INVALIDATED_BY_SKIPPED_RANKING -> outcomeDescription = - "invalidated by skipped ranking" + additionalLogText; - case INVALIDATED_BY_REPEATED_RANKING -> outcomeDescription = - "invalidated by repeated ranking" + additionalLogText; - default -> - // Programming error: we missed a status here - throw new RuntimeException("Unexpected ballot status: " + statusForRound); - } - VoteOutcomeType outcomeType = + final String outcomeDescription = statusForRound == StatusForRound.ACTIVE + ? selectedCandidate + : statusForRound.getTitleCaseKey() + additionalLogText; + final VoteOutcomeType outcomeType = selectedCandidate == null ? VoteOutcomeType.EXHAUSTED : VoteOutcomeType.COUNTED; cvr.logRoundOutcome( currentRoundTally.getRoundNumber(), @@ -1095,6 +1080,7 @@ && isCandidateContinuing(cvr.getCurrentRecipientOfVote())) { // iterate through the rankings in this cvr from most to least preferred. // for each ranking: + // check if it's a final round surplus // if it results in an overvote or undervote, exhaust the cvr // if a selected candidate is continuing, count cvr for that candidate // if no selected candidate is continuing, look at the next ranking @@ -1115,6 +1101,19 @@ && isCandidateContinuing(cvr.getCurrentRecipientOfVote())) { Integer rank = rankCandidatesPair.getKey(); CandidatesAtRanking candidates = rankCandidatesPair.getValue(); + // check for final round surplus + if (config.usesSurpluses() + && config.getNumberOfWinners() == winnerToRound.size()) { + recordSelectionForCastVoteRecord( + cvr, + roundTally, + roundTallyBySlice, + null, + StatusForRound.FINAL_ROUND_SURPLUS, + ""); + break; + } + // check for skipped ranking exhaustion if (config.getMaxSkippedRanksAllowed() != Integer.MAX_VALUE && (rank - lastRankSeen > config.getMaxSkippedRanksAllowed() + 1)) { diff --git a/src/test/resources/network/brightspots/rcv/test_data/2013_minneapolis_park/2013_minneapolis_park_expected_summary.csv b/src/test/resources/network/brightspots/rcv/test_data/2013_minneapolis_park/2013_minneapolis_park_expected_summary.csv index 1c806bc0..874338c4 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/2013_minneapolis_park/2013_minneapolis_park_expected_summary.csv +++ b/src/test/resources/network/brightspots/rcv/test_data/2013_minneapolis_park/2013_minneapolis_park_expected_summary.csv @@ -29,11 +29,12 @@ MARY LYNN MCPHERSON,3373,5.67%,5,3378,5.68%,101,3479,5.85%,0.0000,3479.0000,5.85 ISHMAEL ISRAEL,3305,5.55%,5,3310,5.56%,64,3374,5.67%,0.0000,3374.0000,5.67%,-3374.0000,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 CASPER HILL,1280,2.15%,4,1284,2.15%,-1284,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 Undeclared Write-ins,342,0.57%,-342,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 -Active Ballots,59463,,,59174,,,58876,,,58876.0000,,,57861.0000,,,56780.0000,,,54875.0000,,,51478.0000,,,48418.0000,,,42155.0000,,,41977.0000,, +Active Ballots,59463,,,59174,,,58876,,,58876.0000,,,57861.0000,,,56780.0000,,,54875.0000,,,51478.0000,,,48418.0000,,,42155.0000,,,42154.5192,, Current Round Threshold,14866,,,14866,,,14866,,,14866,,,14866,,,14866,,,14866,,,14866,,,14866,,,14866,,,14866,, Inactive Ballots by Overvotes,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0 Inactive Ballots by Skipped Rankings,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0 -Inactive Ballots by Exhausted Choices,67,,289,356,,298,654,,0.0000,654.0000,,1015.0000,1669.0000,,1081.0000,2750.0000,,1905.0000,4655.0000,,3397.0000,8052.0000,,3060.0000,11112.0000,,6263.0000,17375.0000,,177.5192,17552.5192,,0 +Inactive Ballots by Exhausted Choices,67,,289,356,,298,654,,0.0000,654.0000,,1015.0000,1669.0000,,1081.0000,2750.0000,,1905.0000,4655.0000,,3397.0000,8052.0000,,3060.0000,11112.0000,,6263.0000,17375.0000,,0.0000,17375.0000,,0 Inactive Ballots by Repeated Rankings,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0 -Inactive Ballots Total,67,,289,356,,298,654,,0.0000,654.0000,,1015.0000,1669.0000,,1081.0000,2750.0000,,1905.0000,4655.0000,,3397.0000,8052.0000,,3060.0000,11112.0000,,6263.0000,17375.0000,,177.5192,17552.5192,,0 +Inactive Ballots Total,67,,289,356,,298,654,,0.0000,654.0000,,1015.0000,1669.0000,,1081.0000,2750.0000,,1905.0000,4655.0000,,3397.0000,8052.0000,,3060.0000,11112.0000,,6263.0000,17375.0000,,0.0000,17375.0000,,0 Residual surplus,0,,,0,,,0,,,0,,,0,,,0,,,0,,,0,,,0,,,0,,,0.4808,, +Final Round Surplus,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,177.5192,, diff --git a/src/test/resources/network/brightspots/rcv/test_data/2013_minneapolis_park/2013_minneapolis_park_expected_summary.json b/src/test/resources/network/brightspots/rcv/test_data/2013_minneapolis_park/2013_minneapolis_park_expected_summary.json index 6ee956d5..5ddb8cad 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/2013_minneapolis_park/2013_minneapolis_park_expected_summary.json +++ b/src/test/resources/network/brightspots/rcv/test_data/2013_minneapolis_park/2013_minneapolis_park_expected_summary.json @@ -299,7 +299,8 @@ "threshold" : "14866" }, { "inactiveBallots" : { - "exhaustedChoices" : "17552.5192", + "exhaustedChoices" : "17375.0000", + "finalRoundSurplus" : "177.5192", "overvotes" : "0", "repeatedRankings" : "0", "skippedRankings" : "0" diff --git a/src/test/resources/network/brightspots/rcv/test_data/2013_minneapolis_park_hare/2013_minneapolis_park_hare_expected_summary.csv b/src/test/resources/network/brightspots/rcv/test_data/2013_minneapolis_park_hare/2013_minneapolis_park_hare_expected_summary.csv index 15e13efe..91a5c9fc 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/2013_minneapolis_park_hare/2013_minneapolis_park_hare_expected_summary.csv +++ b/src/test/resources/network/brightspots/rcv/test_data/2013_minneapolis_park_hare/2013_minneapolis_park_hare_expected_summary.csv @@ -29,11 +29,12 @@ MARY LYNN MCPHERSON,3373,5.67%,5,3378,5.68%,101,3479,5.85%,202,3681,6.19%,-3681, ISHMAEL ISRAEL,3305,5.55%,5,3310,5.56%,64,3374,5.67%,-3374,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 CASPER HILL,1280,2.15%,4,1284,2.15%,-1284,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 Undeclared Write-ins,342,0.57%,-342,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 -Active Ballots,59463,,,59174,,,58876,,,57926,,,56924,,,55274,,,52027,,,49968,,,47268,,,45439.0000,, +Active Ballots,59463,,,59174,,,58876,,,57926,,,56924,,,55274,,,52027,,,49968,,,47268,,,47266.2600,, Current Round Threshold,19821,,,19821,,,19821,,,19821,,,19821,,,19821,,,19821,,,19821,,,19821,,,19821,, Inactive Ballots by Overvotes,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0 Inactive Ballots by Skipped Rankings,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0 -Inactive Ballots by Exhausted Choices,67,,289,356,,298,654,,950,1604,,1002,2606,,1650,4256,,3247,7503,,2059,9562,,2700,12262,,1827.2600,14089.2600,,0 +Inactive Ballots by Exhausted Choices,67,,289,356,,298,654,,950,1604,,1002,2606,,1650,4256,,3247,7503,,2059,9562,,2700,12262,,0,12262,,0 Inactive Ballots by Repeated Rankings,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0 -Inactive Ballots Total,67,,289,356,,298,654,,950,1604,,1002,2606,,1650,4256,,3247,7503,,2059,9562,,2700,12262,,1827.2600,14089.2600,,0 +Inactive Ballots Total,67,,289,356,,298,654,,950,1604,,1002,2606,,1650,4256,,3247,7503,,2059,9562,,2700,12262,,0,12262,,0 Residual surplus,0,,,0,,,0,,,0,,,0,,,0,,,0,,,0,,,0,,,1.7400,, +Final Round Surplus,,,,,,,,,,,,,,,,,,,,,,,,,,,,1827.2600,, diff --git a/src/test/resources/network/brightspots/rcv/test_data/2013_minneapolis_park_hare/2013_minneapolis_park_hare_expected_summary.json b/src/test/resources/network/brightspots/rcv/test_data/2013_minneapolis_park_hare/2013_minneapolis_park_hare_expected_summary.json index 8ca6d0fd..8b535721 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/2013_minneapolis_park_hare/2013_minneapolis_park_hare_expected_summary.json +++ b/src/test/resources/network/brightspots/rcv/test_data/2013_minneapolis_park_hare/2013_minneapolis_park_hare_expected_summary.json @@ -284,7 +284,8 @@ "threshold" : "19821" }, { "inactiveBallots" : { - "exhaustedChoices" : "14089.2600", + "exhaustedChoices" : "12262", + "finalRoundSurplus" : "1827.2600", "overvotes" : "0", "repeatedRankings" : "0", "skippedRankings" : "0" diff --git a/src/test/resources/network/brightspots/rcv/test_data/minneapolis_multi_seat_threshold/minneapolis_multi_seat_threshold_expected_pc1_precinct_summary.csv b/src/test/resources/network/brightspots/rcv/test_data/minneapolis_multi_seat_threshold/minneapolis_multi_seat_threshold_expected_pc1_precinct_summary.csv index 4bc6e1f2..807d9474 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/minneapolis_multi_seat_threshold/minneapolis_multi_seat_threshold_expected_pc1_precinct_summary.csv +++ b/src/test/resources/network/brightspots/rcv/test_data/minneapolis_multi_seat_threshold/minneapolis_multi_seat_threshold_expected_pc1_precinct_summary.csv @@ -24,11 +24,12 @@ E,5,20.83%,0,5,20.83%,0.0000,5.0000,20.83%,0.0000,5.0000,20.83%,-5.0000,0,0.0%,0 B,4,16.66%,0,4,16.66%,0,4,16.66%,2.0000,6.0000,25.0%,2.0000,8.0000,33.33%,-1.6000,6.4000,26.66%,0 F,3,12.5%,0,3,12.5%,0.0000,3.0000,12.5%,-3.0000,0,0.0%,0,0,0.0%,0,0,0.0%,0 D,2,8.33%,-2,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 -Active Ballots,24,,,23,,,23.0000,,,23.0000,,,23.0000,,,19.6000,, +Active Ballots,24,,,23,,,23.0000,,,23.0000,,,23.0000,,,23.0000,, Inactive Ballots by Overvotes,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0 -Inactive Ballots by Skipped Rankings,1,,1,2,,0,2,,0,2,,0.0000,2.0000,,0.6000,2.6000,,0 -Inactive Ballots by Exhausted Choices,0,,0,0,,0,0,,0,0,,0.0000,0.0000,,2.8000,2.8000,,0 +Inactive Ballots by Skipped Rankings,1,,1,2,,0,2,,0,2,,0.0000,2.0000,,0.0000,2.0000,,0 +Inactive Ballots by Exhausted Choices,0,,0,0,,0,0,,0,0,,0.0000,0.0000,,0.0000,0.0000,,0 Inactive Ballots by Repeated Rankings,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0 -Inactive Ballots Total,1,,1,2,,0,2,,0,2,,0.0000,2.0000,,3.4000,5.4000,,0 +Inactive Ballots Total,1,,1,2,,0,2,,0,2,,0.0000,2.0000,,0.0000,2.0000,,0 +Final Round Surplus,,,,,,,,,,,,,,,,3.4000,, *Elect/Eliminate decisions are from the full contest. All other results on this report are at the precinct level. diff --git a/src/test/resources/network/brightspots/rcv/test_data/minneapolis_multi_seat_threshold/minneapolis_multi_seat_threshold_expected_pc2_precinct_summary.csv b/src/test/resources/network/brightspots/rcv/test_data/minneapolis_multi_seat_threshold/minneapolis_multi_seat_threshold_expected_pc2_precinct_summary.csv index 6358d5a9..a65d3cc4 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/minneapolis_multi_seat_threshold/minneapolis_multi_seat_threshold_expected_pc2_precinct_summary.csv +++ b/src/test/resources/network/brightspots/rcv/test_data/minneapolis_multi_seat_threshold/minneapolis_multi_seat_threshold_expected_pc2_precinct_summary.csv @@ -24,11 +24,12 @@ E,1,20.0%,0,1,20.0%,0.0000,1.0000,20.0%,0.0000,1.0000,20.0%,-1.0000,0,0.0%,0,0,0 B,1,20.0%,0,1,20.0%,0,1,20.0%,0,1,20.0%,1,2,40.0%,-0.4000,1.6000,32.0%,0 F,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 D,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 -Active Ballots,5,,,5,,,5.0000,,,5.0000,,,5.0000,,,4.4000,, +Active Ballots,5,,,5,,,5.0000,,,5.0000,,,5.0000,,,5.0000,, Inactive Ballots by Overvotes,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0 Inactive Ballots by Skipped Rankings,0,,0,0,,0,0,,0,0,,0.0000,0.0000,,0.0000,0.0000,,0 -Inactive Ballots by Exhausted Choices,0,,0,0,,0,0,,0,0,,0.0000,0.0000,,0.6000,0.6000,,0 +Inactive Ballots by Exhausted Choices,0,,0,0,,0,0,,0,0,,0.0000,0.0000,,0.0000,0.0000,,0 Inactive Ballots by Repeated Rankings,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0 -Inactive Ballots Total,0,,0,0,,0,0,,0,0,,0.0000,0.0000,,0.6000,0.6000,,0 +Inactive Ballots Total,0,,0,0,,0,0,,0,0,,0.0000,0.0000,,0.0000,0.0000,,0 +Final Round Surplus,,,,,,,,,,,,,,,,0.6000,, *Elect/Eliminate decisions are from the full contest. All other results on this report are at the precinct level. diff --git a/src/test/resources/network/brightspots/rcv/test_data/minneapolis_multi_seat_threshold/minneapolis_multi_seat_threshold_expected_pc2_precinct_summary.json b/src/test/resources/network/brightspots/rcv/test_data/minneapolis_multi_seat_threshold/minneapolis_multi_seat_threshold_expected_pc2_precinct_summary.json index b5aff97d..13a04be5 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/minneapolis_multi_seat_threshold/minneapolis_multi_seat_threshold_expected_pc2_precinct_summary.json +++ b/src/test/resources/network/brightspots/rcv/test_data/minneapolis_multi_seat_threshold/minneapolis_multi_seat_threshold_expected_pc2_precinct_summary.json @@ -117,7 +117,8 @@ "threshold" : "8" }, { "inactiveBallots" : { - "exhaustedChoices" : "0.6000", + "exhaustedChoices" : "0.0000", + "finalRoundSurplus" : "0.6000", "overvotes" : "0", "repeatedRankings" : "0", "skippedRankings" : "0.0000" diff --git a/src/test/resources/network/brightspots/rcv/test_data/minneapolis_multi_seat_threshold/minneapolis_multi_seat_threshold_expected_summary.csv b/src/test/resources/network/brightspots/rcv/test_data/minneapolis_multi_seat_threshold/minneapolis_multi_seat_threshold_expected_summary.csv index 73c2dfe2..13a43222 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/minneapolis_multi_seat_threshold/minneapolis_multi_seat_threshold_expected_summary.csv +++ b/src/test/resources/network/brightspots/rcv/test_data/minneapolis_multi_seat_threshold/minneapolis_multi_seat_threshold_expected_summary.csv @@ -24,10 +24,11 @@ E,6,20.68%,0,6,20.68%,0.0000,6.0000,20.68%,0.0000,6.0000,20.68%,-6.0000,0,0.0%,0 B,5,17.24%,0,5,17.24%,0,5,17.24%,2.0000,7.0000,24.13%,3.0000,10.0000,34.48%,-2.0000,8.0000,27.58%,0 F,3,10.34%,0,3,10.34%,0.0000,3.0000,10.34%,-3.0000,0,0.0%,0,0,0.0%,0,0,0.0%,0 D,2,6.89%,-2,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 -Active Ballots,29,,,28,,,28.0000,,,28.0000,,,28.0000,,,24.0000,, +Active Ballots,29,,,28,,,28.0000,,,28.0000,,,28.0000,,,28.0000,, Current Round Threshold,8,,,8,,,8,,,8,,,8,,,8,, Inactive Ballots by Overvotes,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0 -Inactive Ballots by Skipped Rankings,1,,1,2,,0,2,,0,2,,0.0000,2.0000,,0.6000,2.6000,,0 -Inactive Ballots by Exhausted Choices,0,,0,0,,0,0,,0,0,,0.0000,0.0000,,3.4000,3.4000,,0 +Inactive Ballots by Skipped Rankings,1,,1,2,,0,2,,0,2,,0.0000,2.0000,,0.0000,2.0000,,0 +Inactive Ballots by Exhausted Choices,0,,0,0,,0,0,,0,0,,0.0000,0.0000,,0.0000,0.0000,,0 Inactive Ballots by Repeated Rankings,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0 -Inactive Ballots Total,1,,1,2,,0,2,,0,2,,0.0000,2.0000,,4.0000,6.0000,,0 +Inactive Ballots Total,1,,1,2,,0,2,,0,2,,0.0000,2.0000,,0.0000,2.0000,,0 +Final Round Surplus,,,,,,,,,,,,,,,,4.0000,, diff --git a/src/test/resources/network/brightspots/rcv/test_data/minneapolis_multi_seat_threshold/minneapolis_multi_seat_threshold_expected_summary.json b/src/test/resources/network/brightspots/rcv/test_data/minneapolis_multi_seat_threshold/minneapolis_multi_seat_threshold_expected_summary.json index d6b57821..682d324d 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/minneapolis_multi_seat_threshold/minneapolis_multi_seat_threshold_expected_summary.json +++ b/src/test/resources/network/brightspots/rcv/test_data/minneapolis_multi_seat_threshold/minneapolis_multi_seat_threshold_expected_summary.json @@ -123,10 +123,11 @@ "threshold" : "8" }, { "inactiveBallots" : { - "exhaustedChoices" : "3.4000", + "exhaustedChoices" : "0.0000", + "finalRoundSurplus" : "4.0000", "overvotes" : "0", "repeatedRankings" : "0", - "skippedRankings" : "2.6000" + "skippedRankings" : "2.0000" }, "round" : 6, "tally" : { diff --git a/src/test/resources/network/brightspots/rcv/test_data/more_winners_than_candidates/more_winners_than_candidates_expected_summary.csv b/src/test/resources/network/brightspots/rcv/test_data/more_winners_than_candidates/more_winners_than_candidates_expected_summary.csv index 4304d9f4..5af221b3 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/more_winners_than_candidates/more_winners_than_candidates_expected_summary.csv +++ b/src/test/resources/network/brightspots/rcv/test_data/more_winners_than_candidates/more_winners_than_candidates_expected_summary.csv @@ -29,3 +29,4 @@ Inactive Ballots by Skipped Rankings,0,,0 Inactive Ballots by Exhausted Choices,0,,0 Inactive Ballots by Repeated Rankings,0,,0 Inactive Ballots Total,0,,0 +Final Round Surplus,,, diff --git a/src/test/resources/network/brightspots/rcv/test_data/more_winners_than_candidates/more_winners_than_candidates_expected_summary.json b/src/test/resources/network/brightspots/rcv/test_data/more_winners_than_candidates/more_winners_than_candidates_expected_summary.json index 1f86f83d..bc21bf73 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/more_winners_than_candidates/more_winners_than_candidates_expected_summary.json +++ b/src/test/resources/network/brightspots/rcv/test_data/more_winners_than_candidates/more_winners_than_candidates_expected_summary.json @@ -10,6 +10,7 @@ "results" : [ { "inactiveBallots" : { "exhaustedChoices" : "0", + "finalRoundSurplus" : "0", "overvotes" : "0", "repeatedRankings" : "0", "skippedRankings" : "0" diff --git a/src/test/resources/network/brightspots/rcv/test_data/multi_seat_uwi_test/multi_seat_uwi_test_expected_summary.csv b/src/test/resources/network/brightspots/rcv/test_data/multi_seat_uwi_test/multi_seat_uwi_test_expected_summary.csv index f22cc9b9..d2c0e8ff 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/multi_seat_uwi_test/multi_seat_uwi_test_expected_summary.csv +++ b/src/test/resources/network/brightspots/rcv/test_data/multi_seat_uwi_test/multi_seat_uwi_test_expected_summary.csv @@ -29,3 +29,4 @@ Inactive Ballots by Skipped Rankings,0,,0,0,,0,0,,0,0,,0,0,,0 Inactive Ballots by Exhausted Choices,0,,4,4,,0.0000,4.0000,,2.0000,6.0000,,0.0000,6.0000,,0 Inactive Ballots by Repeated Rankings,0,,0,0,,0,0,,0,0,,0,0,,0 Inactive Ballots Total,0,,4,4,,0.0000,4.0000,,2.0000,6.0000,,0.0000,6.0000,,0 +Final Round Surplus,,,,,,,,,,,,,0.0000,, diff --git a/src/test/resources/network/brightspots/rcv/test_data/multi_seat_uwi_test/multi_seat_uwi_test_expected_summary.json b/src/test/resources/network/brightspots/rcv/test_data/multi_seat_uwi_test/multi_seat_uwi_test_expected_summary.json index 962b59af..d4041fe4 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/multi_seat_uwi_test/multi_seat_uwi_test_expected_summary.json +++ b/src/test/resources/network/brightspots/rcv/test_data/multi_seat_uwi_test/multi_seat_uwi_test_expected_summary.json @@ -86,6 +86,7 @@ }, { "inactiveBallots" : { "exhaustedChoices" : "6.0000", + "finalRoundSurplus" : "0.0000", "overvotes" : "0", "repeatedRankings" : "0", "skippedRankings" : "0" diff --git a/src/test/resources/network/brightspots/rcv/test_data/test_set_allow_only_one_winner_per_round/test_set_allow_only_one_winner_per_round_expected_summary.csv b/src/test/resources/network/brightspots/rcv/test_data/test_set_allow_only_one_winner_per_round/test_set_allow_only_one_winner_per_round_expected_summary.csv index e9be18f1..edf4fddb 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/test_set_allow_only_one_winner_per_round/test_set_allow_only_one_winner_per_round_expected_summary.csv +++ b/src/test/resources/network/brightspots/rcv/test_data/test_set_allow_only_one_winner_per_round/test_set_allow_only_one_winner_per_round_expected_summary.csv @@ -21,12 +21,13 @@ Elected,A,,,B,,,C,,,,, A,3,33.33%,-0.7499,2.2501,25.0%,0.0000,2.2501,25.0%,0.0000,2.2501,25.0%,0 B,3,33.33%,0.7497,3.7497,41.66%,-1.4996,2.2501,25.0%,0.0000,2.2501,25.0%,0 C,3,33.33%,0,3,33.33%,1.4994,4.4994,49.99%,-2.2493,2.2501,25.0%,0 -D,0,0.0%,0,0,0.0%,0,0,0.0%,2.0994,2.0994,23.32%,0 -Active Ballots,9,,,8.9998,,,8.9996,,,8.8497,, +D,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 +Active Ballots,9,,,8.9998,,,8.9996,,,8.9994,, Current Round Threshold,2.2501,,,2.2501,,,2.2501,,,2.2501,, Inactive Ballots by Overvotes,0,,0,0,,0,0,,0,0,,0 Inactive Ballots by Skipped Rankings,0,,0,0,,0,0,,0,0,,0 -Inactive Ballots by Exhausted Choices,0,,0,0,,0,0,,0.1497,0.1497,,0 +Inactive Ballots by Exhausted Choices,0,,0,0,,0,0,,0,0,,0 Inactive Ballots by Repeated Rankings,0,,0,0,,0,0,,0,0,,0 -Inactive Ballots Total,0,,0,0,,0,0,,0.1497,0.1497,,0 +Inactive Ballots Total,0,,0,0,,0,0,,0,0,,0 Residual surplus,0,,,0.0002,,,0.0004,,,0.0006,, +Final Round Surplus,,,,,,,,,,2.2491,, diff --git a/src/test/resources/network/brightspots/rcv/test_data/test_set_allow_only_one_winner_per_round/test_set_allow_only_one_winner_per_round_expected_summary.json b/src/test/resources/network/brightspots/rcv/test_data/test_set_allow_only_one_winner_per_round/test_set_allow_only_one_winner_per_round_expected_summary.json index 4032b59f..fed63d99 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/test_set_allow_only_one_winner_per_round/test_set_allow_only_one_winner_per_round_expected_summary.json +++ b/src/test/resources/network/brightspots/rcv/test_data/test_set_allow_only_one_winner_per_round/test_set_allow_only_one_winner_per_round_expected_summary.json @@ -68,15 +68,15 @@ "tallyResults" : [ { "elected" : "C", "transfers" : { - "D" : "2.0994", - "exhausted" : "0.1497", + "exhausted" : "2.2491", "residual surplus" : "0.0002" } } ], "threshold" : "2.2501" }, { "inactiveBallots" : { - "exhaustedChoices" : "0.1497", + "exhaustedChoices" : "0", + "finalRoundSurplus" : "2.2491", "overvotes" : "0", "repeatedRankings" : "0", "skippedRankings" : "0" @@ -86,7 +86,7 @@ "A" : "2.2501", "B" : "2.2501", "C" : "2.2501", - "D" : "2.0994" + "D" : "0" }, "tallyResults" : [ ], "threshold" : "2.2501" @@ -98,4 +98,4 @@ "totalNumBallots" : "9", "undervotes" : 0 } -} \ No newline at end of file +} diff --git a/src/test/resources/network/brightspots/rcv/test_data/test_set_multi_winner_fractional_threshold/test_set_multi_winner_fractional_threshold_expected_summary.csv b/src/test/resources/network/brightspots/rcv/test_data/test_set_multi_winner_fractional_threshold/test_set_multi_winner_fractional_threshold_expected_summary.csv index a26bfc6a..257b5afa 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/test_set_multi_winner_fractional_threshold/test_set_multi_winner_fractional_threshold_expected_summary.csv +++ b/src/test/resources/network/brightspots/rcv/test_data/test_set_multi_winner_fractional_threshold/test_set_multi_winner_fractional_threshold_expected_summary.csv @@ -24,11 +24,12 @@ Candidate E Name,6,20.68%,0,6,20.68%,0.6559,6.6559,22.95%,0.0000,6.6559,22.95%,- Candidate B Name,5,17.24%,0,5,17.24%,0,5,17.24%,2.0937,7.0937,24.46%,3.0000,10.0937,34.8%,-2.8436,7.2501,25.0%,0 Candidate F Name,3,10.34%,0,3,10.34%,0.0937,3.0937,10.66%,-3.0937,0,0.0%,0,0,0.0%,0,0,0.0%,0 Candidate D Name,2,6.89%,-2,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 -Active Ballots,29,,,28,,,27.9997,,,27.9997,,,27.6249,,,21.7503,, +Active Ballots,29,,,28,,,27.9997,,,27.9997,,,27.6249,,,27.6244,, Current Round Threshold,7.2501,,,7.2501,,,7.2501,,,7.2501,,,7.2501,,,7.2501,, Inactive Ballots by Overvotes,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0 -Inactive Ballots by Skipped Rankings,1,,1,2,,0,2,,0,2,,0.1874,2.1874,,0.8582,3.0456,,0 -Inactive Ballots by Exhausted Choices,0,,0,0,,0,0,,0,0,,0.1874,0.1874,,5.0159,5.2033,,0 +Inactive Ballots by Skipped Rankings,1,,1,2,,0,2,,0,2,,0.1874,2.1874,,0.0000,2.1874,,0 +Inactive Ballots by Exhausted Choices,0,,0,0,,0,0,,0,0,,0.1874,0.1874,,0.0000,0.1874,,0 Inactive Ballots by Repeated Rankings,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0 -Inactive Ballots Total,1,,1,2,,0,2,,0,2,,0.3748,2.3748,,5.8741,8.2489,,0 +Inactive Ballots Total,1,,1,2,,0,2,,0,2,,0.3748,2.3748,,0.0000,2.3748,,0 Residual surplus,0,,,0,,,0.0003,,,0.0003,,,0.0003,,,0.0008,, +Final Round Surplus,,,,,,,,,,,,,,,,5.8741,, diff --git a/src/test/resources/network/brightspots/rcv/test_data/test_set_multi_winner_fractional_threshold/test_set_multi_winner_fractional_threshold_expected_summary.json b/src/test/resources/network/brightspots/rcv/test_data/test_set_multi_winner_fractional_threshold/test_set_multi_winner_fractional_threshold_expected_summary.json index 021bbb99..982d420c 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/test_set_multi_winner_fractional_threshold/test_set_multi_winner_fractional_threshold_expected_summary.json +++ b/src/test/resources/network/brightspots/rcv/test_data/test_set_multi_winner_fractional_threshold/test_set_multi_winner_fractional_threshold_expected_summary.json @@ -130,10 +130,11 @@ "threshold" : "7.2501" }, { "inactiveBallots" : { - "exhaustedChoices" : "5.2033", + "exhaustedChoices" : "0.1874", + "finalRoundSurplus" : "5.8741", "overvotes" : "0", "repeatedRankings" : "0", - "skippedRankings" : "3.0456" + "skippedRankings" : "2.1874" }, "round" : 6, "tally" : { diff --git a/src/test/resources/network/brightspots/rcv/test_data/test_set_multi_winner_whole_threshold/test_set_multi_winner_whole_threshold_expected_summary.csv b/src/test/resources/network/brightspots/rcv/test_data/test_set_multi_winner_whole_threshold/test_set_multi_winner_whole_threshold_expected_summary.csv index f5daba6a..b90c40da 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/test_set_multi_winner_whole_threshold/test_set_multi_winner_whole_threshold_expected_summary.csv +++ b/src/test/resources/network/brightspots/rcv/test_data/test_set_multi_winner_whole_threshold/test_set_multi_winner_whole_threshold_expected_summary.csv @@ -24,11 +24,12 @@ Candidate B Name,5,17.24%,0,5,17.24%,0,5,17.24%,2.1111,7.1111,24.52%,2.1111,9.22 Candidate E Name,4,13.79%,0.7777,4.7777,16.47%,1.0000,5.7777,19.92%,0.0000,5.7777,19.92%,-5.7777,0,0.0%,0,0,0.0%,0 Candidate F Name,3,10.34%,0.1111,3.1111,10.72%,0.1111,3.2222,11.11%,-3.2222,0,0.0%,0,0,0.0%,0,0,0.0%,0 Candidate D Name,2,6.89%,0.1111,2.1111,7.27%,-2.1111,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 -Active Ballots,29,,,28.9999,,,27.9999,,,27.9999,,,27.5555,,,24.0000,, +Active Ballots,29,,,28.9999,,,27.9999,,,27.9999,,,27.5555,,,27.5549,, Current Round Threshold,8,,,8,,,8,,,8,,,8,,,8,, Inactive Ballots by Overvotes,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0 -Inactive Ballots by Skipped Rankings,1,,0,1,,1,2,,0,2,,0.2222,2.2222,,0.4908,2.7130,,0 -Inactive Ballots by Exhausted Choices,0,,0,0,,0,0,,0,0,,0.2222,0.2222,,3.0641,3.2863,,0 +Inactive Ballots by Skipped Rankings,1,,0,1,,1,2,,0,2,,0.2222,2.2222,,0.0000,2.2222,,0 +Inactive Ballots by Exhausted Choices,0,,0,0,,0,0,,0,0,,0.2222,0.2222,,0.0000,0.2222,,0 Inactive Ballots by Repeated Rankings,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0 -Inactive Ballots Total,1,,0,1,,1,2,,0,2,,0.4444,2.4444,,3.5549,5.9993,,0 +Inactive Ballots Total,1,,0,1,,1,2,,0,2,,0.4444,2.4444,,0.0000,2.4444,,0 Residual surplus,0,,,0.0001,,,0.0001,,,0.0001,,,0.0001,,,0.0007,, +Final Round Surplus,,,,,,,,,,,,,,,,3.5549,, diff --git a/src/test/resources/network/brightspots/rcv/test_data/test_set_multi_winner_whole_threshold/test_set_multi_winner_whole_threshold_expected_summary.json b/src/test/resources/network/brightspots/rcv/test_data/test_set_multi_winner_whole_threshold/test_set_multi_winner_whole_threshold_expected_summary.json index 354f47a4..d8171b20 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/test_set_multi_winner_whole_threshold/test_set_multi_winner_whole_threshold_expected_summary.json +++ b/src/test/resources/network/brightspots/rcv/test_data/test_set_multi_winner_whole_threshold/test_set_multi_winner_whole_threshold_expected_summary.json @@ -133,10 +133,11 @@ "threshold" : "8" }, { "inactiveBallots" : { - "exhaustedChoices" : "3.2863", + "exhaustedChoices" : "0.2222", + "finalRoundSurplus" : "3.5549", "overvotes" : "0", "repeatedRankings" : "0", - "skippedRankings" : "2.7130" + "skippedRankings" : "2.2222" }, "round" : 6, "tally" : {