Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

STV last round use "Final Round Surplus" rather than inactive #884

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 61 additions & 6 deletions src/main/java/network/brightspots/rcv/CastVoteRecord.java
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,67 @@ Map<String, BigDecimal> 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 {
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/network/brightspots/rcv/ContestConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,10 @@ boolean isNonIntegerWinningThresholdEnabled() {
return rawConfig.rules.nonIntegerWinningThreshold;
}

boolean usesSurpluses() {
return getNumberOfWinners() > 1 && !isMultiSeatBottomsUpUntilNWinnersEnabled();
}

yezr marked this conversation as resolved.
Show resolved Hide resolved
boolean isHareQuotaEnabled() {
return rawConfig.rules.hareQuota;
}
Expand Down
66 changes: 36 additions & 30 deletions src/main/java/network/brightspots/rcv/ResultsWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -78,6 +79,14 @@ class ResultsWriter {
private String timestampString;
// map from round number to residual surplus generated in that round
private Map<Integer, BigDecimal> roundToResidualSurplus;
// statuses to print in all summary files
// (additional fields are added if needed in specific summary filetypes)
private static final List<StatusForRound> 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")
Expand Down Expand Up @@ -382,20 +391,9 @@ private void generateSummarySpreadsheet(
csvPrinter.println();
}

List<Pair<String, StatusForRound>> 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<String, StatusForRound> 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);
Expand Down Expand Up @@ -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. "
Expand Down Expand Up @@ -1012,23 +1025,16 @@ private Map<String, BigDecimal> updateCandidateNamesInTally(RoundTally roundSumm
}

private Map<String, BigDecimal> getInactiveJsonMap(RoundTally roundTally) {
Map<String, BigDecimal> inactiveMap = new HashMap<>();
Pair<String, StatusForRound>[] 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<String, StatusForRound> statusToPrint : statusesToPrint) {
inactiveMap.put(
statusToPrint.getKey(), roundTally.getBallotStatusTally(statusToPrint.getValue()));
Map<String, BigDecimal> 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
Expand All @@ -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
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/network/brightspots/rcv/RoundTally.java
Original file line number Diff line number Diff line change
Expand Up @@ -204,14 +204,15 @@ public List<String> 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() {
Expand Down
43 changes: 21 additions & 22 deletions src/main/java/network/brightspots/rcv/Tabulator.java
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ Set<String> 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)
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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
Expand All @@ -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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,,
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,8 @@
"threshold" : "14866"
}, {
"inactiveBallots" : {
"exhaustedChoices" : "17552.5192",
"exhaustedChoices" : "17375.0000",
"finalRoundSurplus" : "177.5192",
"overvotes" : "0",
"repeatedRankings" : "0",
"skippedRankings" : "0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,,
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,8 @@
"threshold" : "19821"
}, {
"inactiveBallots" : {
"exhaustedChoices" : "14089.2600",
"exhaustedChoices" : "12262",
"finalRoundSurplus" : "1827.2600",
"overvotes" : "0",
"repeatedRankings" : "0",
"skippedRankings" : "0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@
"threshold" : "8"
}, {
"inactiveBallots" : {
"exhaustedChoices" : "0.6000",
"exhaustedChoices" : "0.0000",
"finalRoundSurplus" : "0.6000",
"overvotes" : "0",
"repeatedRankings" : "0",
"skippedRankings" : "0.0000"
Expand Down
Loading
Loading