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

correct percentages for STV and first-round-determines-threshold #883

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();
}

boolean isHareQuotaEnabled() {
return rawConfig.rules.hareQuota;
}
Expand Down
75 changes: 43 additions & 32 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 @@ -333,8 +342,13 @@ private void generateSummarySpreadsheet(
// Vote count
csvPrinter.print(thisRoundTally);

// Vote %
BigDecimal votePctDivisor = roundTallies.get(round).activeAndLockedInBallotSum();
// Vote % (divisor is 1st round total in STV or 1st round determines threshold)
BigDecimal votePctDivisor;
if (config.isSingleWinnerEnabled() && !config.isFirstRoundDeterminesThresholdEnabled()) {
votePctDivisor = roundTallies.get(round).activeAndLockedInBallotSum();
} else {
votePctDivisor = roundTallies.get(1).activeAndLockedInBallotSum();
}
if (votePctDivisor != BigDecimal.ZERO) {
// Turn a decimal into a human-readable percentage (e.g. 0.1234 -> 12.34%)
BigDecimal divDecimal = thisRoundTally.divide(votePctDivisor, MathContext.DECIMAL32);
Expand Down Expand Up @@ -377,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 @@ -450,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 @@ -1007,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 @@ -1042,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 @@ -18,22 +18,23 @@ Number of Undervotes (No Rankings),20571
Rounds,Round 1 Votes,% of vote,transfer,Round 2 Votes,% of vote,transfer,Round 3 Votes,% of vote,transfer,Round 4 Votes,% of vote,transfer,Round 5 Votes,% of vote,transfer,Round 6 Votes,% of vote,transfer,Round 7 Votes,% of vote,transfer,Round 8 Votes,% of vote,transfer,Round 9 Votes,% of vote,transfer,Round 10 Votes,% of vote,transfer,Round 11 Votes,% of vote,transfer
Eliminated,Undeclared Write-ins,,,CASPER HILL,,,,,,ISHMAEL ISRAEL,,,MARY LYNN MCPHERSON,,,STEVE BARLAND,,,HASHIM YONIS,,,JASON STONE,,,TOM NORDYKE,,,,,,,,
Elected,,,,,,,JOHN ERWIN,,,,,,,,,,,,,,,,,,,,,ANNIE YOUNG; MEG FORNEY,,,,,
JOHN ERWIN,14678,24.68%,6,14684,24.81%,182,14866,25.24%,0.0000,14866.0000,25.24%,0.0000,14866.0000,25.69%,0.0000,14866.0000,26.18%,0.0000,14866.0000,27.09%,0.0000,14866.0000,28.87%,0.0000,14866.0000,30.7%,0.0000,14866.0000,35.26%,0.0000,14866.0000,35.41%,0
ANNIE YOUNG,9294,15.62%,8,9302,15.71%,150,9452,16.05%,0.0000,9452.0000,16.05%,603.0000,10055.0000,17.37%,999.0000,11054.0000,19.46%,471.0000,11525.0000,21.0%,502.0000,12027.0000,23.36%,1859.0000,13886.0000,28.67%,1158.0000,15044.0000,35.68%,-178.0000,14866,35.41%,0
MEG FORNEY,7856,13.21%,8,7864,13.28%,167,8031,13.64%,0.0000,8031.0000,13.64%,390.0000,8421.0000,14.55%,738.0000,9159.0000,16.13%,670.0000,9829.0000,17.91%,327.0000,10156.0000,19.72%,809.0000,10965.0000,22.64%,1280.0000,12245.0000,29.04%,0.0000,12245.0000,29.17%,0
TOM NORDYKE,6511,10.94%,3,6514,11.0%,81,6595,11.2%,0.0000,6595.0000,11.2%,164.0000,6759.0000,11.68%,242.0000,7001.0000,12.33%,534.0000,7535.0000,13.73%,153.0000,7688.0000,14.93%,1013.0000,8701.0000,17.97%,-8701.0000,0,0.0%,0,0,0.0%,0
JASON STONE,5357,9.0%,7,5364,9.06%,113,5477,9.3%,0.0000,5477.0000,9.3%,342.0000,5819.0000,10.05%,278.0000,6097.0000,10.73%,452.0000,6549.0000,11.93%,192.0000,6741.0000,13.09%,-6741.0000,0,0.0%,0,0,0.0%,0,0,0.0%,0
HASHIM YONIS,3762,6.32%,5,3767,6.36%,32,3799,6.45%,0.0000,3799.0000,6.45%,547.0000,4346.0000,7.51%,144.0000,4490.0000,7.9%,81.0000,4571.0000,8.32%,-4571.0000,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0
STEVE BARLAND,3705,6.23%,2,3707,6.26%,96,3803,6.45%,0.0000,3803.0000,6.45%,98.0000,3901.0000,6.74%,212.0000,4113.0000,7.24%,-4113.0000,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0
MARY LYNN MCPHERSON,3373,5.67%,5,3378,5.7%,101,3479,5.9%,0.0000,3479.0000,5.9%,215.0000,3694.0000,6.38%,-3694.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
ISHMAEL ISRAEL,3305,5.55%,5,3310,5.59%,64,3374,5.73%,0.0000,3374.0000,5.73%,-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.16%,-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
JOHN ERWIN,14678,24.68%,6,14684,24.69%,182,14866,25.0%,0.0000,14866.0000,25.0%,0.0000,14866.0000,25.0%,0.0000,14866.0000,25.0%,0.0000,14866.0000,25.0%,0.0000,14866.0000,25.0%,0.0000,14866.0000,25.0%,0.0000,14866.0000,25.0%,0.0000,14866.0000,25.0%,0
ANNIE YOUNG,9294,15.62%,8,9302,15.64%,150,9452,15.89%,0.0000,9452.0000,15.89%,603.0000,10055.0000,16.9%,999.0000,11054.0000,18.58%,471.0000,11525.0000,19.38%,502.0000,12027.0000,20.22%,1859.0000,13886.0000,23.35%,1158.0000,15044.0000,25.29%,-178.0000,14866,25.0%,0
MEG FORNEY,7856,13.21%,8,7864,13.22%,167,8031,13.5%,0.0000,8031.0000,13.5%,390.0000,8421.0000,14.16%,738.0000,9159.0000,15.4%,670.0000,9829.0000,16.52%,327.0000,10156.0000,17.07%,809.0000,10965.0000,18.44%,1280.0000,12245.0000,20.59%,0.0000,12245.0000,20.59%,0
TOM NORDYKE,6511,10.94%,3,6514,10.95%,81,6595,11.09%,0.0000,6595.0000,11.09%,164.0000,6759.0000,11.36%,242.0000,7001.0000,11.77%,534.0000,7535.0000,12.67%,153.0000,7688.0000,12.92%,1013.0000,8701.0000,14.63%,-8701.0000,0,0.0%,0,0,0.0%,0
JASON STONE,5357,9.0%,7,5364,9.02%,113,5477,9.21%,0.0000,5477.0000,9.21%,342.0000,5819.0000,9.78%,278.0000,6097.0000,10.25%,452.0000,6549.0000,11.01%,192.0000,6741.0000,11.33%,-6741.0000,0,0.0%,0,0,0.0%,0,0,0.0%,0
HASHIM YONIS,3762,6.32%,5,3767,6.33%,32,3799,6.38%,0.0000,3799.0000,6.38%,547.0000,4346.0000,7.3%,144.0000,4490.0000,7.55%,81.0000,4571.0000,7.68%,-4571.0000,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0
STEVE BARLAND,3705,6.23%,2,3707,6.23%,96,3803,6.39%,0.0000,3803.0000,6.39%,98.0000,3901.0000,6.56%,212.0000,4113.0000,6.91%,-4113.0000,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0
MARY LYNN MCPHERSON,3373,5.67%,5,3378,5.68%,101,3479,5.85%,0.0000,3479.0000,5.85%,215.0000,3694.0000,6.21%,-3694.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
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
Loading
Loading