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

Updates to the per-slice CSV #872

Merged
merged 9 commits into from
Sep 25, 2024
66 changes: 42 additions & 24 deletions src/main/java/network/brightspots/rcv/ResultsWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -258,9 +258,11 @@ ResultsWriter setTimestampString(String timestampString) {
// creates summary files for the votes split by a TabulateBySlice
// param: roundTalliesBySlice is map from a slice type to the round-by-round vote tallies
// param: tallyTransfersBySlice is a map from a slice type to tally transfers for that slice
// param: candidateOrder is to allow a consistent ordering of candidates, including across slices
void generateBySliceSummaryFiles(
Tabulator.BreakdownBySlice<RoundTallies> roundTalliesBySlice,
Tabulator.BreakdownBySlice<TallyTransfers> tallyTransfersBySlice)
Tabulator.BreakdownBySlice<TallyTransfers> tallyTransfersBySlice,
List<String> candidateOrder)
throws IOException {
for (ContestConfig.TabulateBySlice slice : config.enabledSlices()) {
Set<String> filenames = new HashSet<>();
Expand All @@ -271,20 +273,24 @@ void generateBySliceSummaryFiles(
String sliceFileString = getFileStringForSlice(slice, sliceId, filenames);
String outputPath = getOutputFilePathFromInstance(
String.format("%s_summary", sliceFileString));
generateSummarySpreadsheet(roundTallies, slice, sliceId, outputPath);
generateSummarySpreadsheet(roundTallies, candidateOrder, slice, sliceId, outputPath);
generateSummaryJson(roundTallies, tallyTransfers, slice, sliceId, outputPath);
}
}
}

// create a summary spreadsheet .csv file
// param: roundTallies is the round-by-count count of votes per candidate
// param: candidateOrder is to allow a consistent ordering of candidates, including across slices
// param: slice indicates which type of slice we're reporting results for (null means all)
// param: sliceId indicates the specific slice ID we're reporting results for (null means all)
// param: outputPath is the path to the output file, minus its extension
private void generateSummarySpreadsheet(
RoundTallies roundTallies, TabulateBySlice slice, String sliceId, String outputPath)
throws IOException {
RoundTallies roundTallies,
List<String> candidateOrder,
TabulateBySlice slice,
String sliceId,
String outputPath) throws IOException {
AuditableFile csvFile = new AuditableFile(outputPath + ".csv");
Logger.info("Generating summary spreadsheet: %s...", csvFile.getAbsolutePath());

Expand All @@ -310,16 +316,11 @@ private void generateSummarySpreadsheet(
}
csvPrinter.println();

// actions don't make sense in individual by-slice results
if (isNullOrBlank(sliceId)) {
addActionRows(csvPrinter);
}

// Get all candidates sorted by their first round tally. This determines the display order.
List<String> sortedCandidates = roundTallies.get(1).getSortedCandidatesByTally();
boolean isSlice = !isNullOrBlank(sliceId);
addActionRows(csvPrinter, isSlice);

// For each candidate: for each round: output total votes
for (String candidate : sortedCandidates) {
for (String candidate : candidateOrder) {
String candidateDisplayName = config.getNameForCandidate(candidate);
csvPrinter.print(candidateDisplayName);
for (int round = 1; round <= numRounds; round++) {
Expand Down Expand Up @@ -366,13 +367,15 @@ private void generateSummarySpreadsheet(
}
csvPrinter.println();

csvPrinter.print("Current Round Threshold");
for (int round = 1; round <= numRounds; round++) {
csvPrinter.print(roundTallies.get(round).getWinningThreshold());
csvPrinter.print("");
csvPrinter.print("");
if (!isSlice) {
csvPrinter.print("Current Round Threshold");
for (int round = 1; round <= numRounds; round++) {
csvPrinter.print(roundTallies.get(round).getWinningThreshold());
csvPrinter.print("");
csvPrinter.print("");
}
csvPrinter.println();
}
csvPrinter.println();

List<Pair<String, StatusForRound>> statusesToPrint = new ArrayList<>();
statusesToPrint.add(new Pair<>("Overvotes",
Expand Down Expand Up @@ -447,6 +450,13 @@ private void generateSummarySpreadsheet(
csvPrinter.println();
}

if (isSlice) {
csvPrinter.println();
csvPrinter.print(String.format("*Elect/Eliminate decisions are from the full contest. "
+ "All other results on this report are at the %s level.", slice.toLowerString()));
csvPrinter.println();
}

try {
csvPrinter.flush();
csvPrinter.close();
Expand All @@ -459,11 +469,11 @@ private void generateSummarySpreadsheet(
}

// "action" rows describe which candidates were eliminated or elected
private void addActionRows(CSVPrinter csvPrinter) throws IOException {
csvPrinter.print("Eliminated");
private void addActionRows(CSVPrinter csvPrinter, boolean withAsterisk) throws IOException {
csvPrinter.print(withAsterisk ? "Eliminated*" : "Eliminated");
printActionSummary(csvPrinter, roundToEliminatedCandidates);

csvPrinter.print("Elected");
csvPrinter.print(withAsterisk ? "Elected*" : "Elected");
printActionSummary(csvPrinter, roundToWinningCandidates);
}

Expand Down Expand Up @@ -533,19 +543,27 @@ private void addContestInformationRows(CSVPrinter csvPrinter,
winners.add(config.getNameForCandidate(candidateName));
}
}

csvPrinter.printRecord("Winner(s)", String.join(", ", winners));
csvPrinter.printRecord("Final Threshold", winningThreshold);

if (!isNullOrBlank(sliceId)) {
// Only silces print the slice information
artoonie marked this conversation as resolved.
Show resolved Hide resolved
csvPrinter.printRecord(slice, sliceId);
} else {
// Only non-slices print threshold information
csvPrinter.printRecord("Final Threshold", winningThreshold);
}

csvPrinter.println();
}

// creates a summary spreadsheet and JSON for the full contest (as opposed to a specific slice)
void generateOverallSummaryFiles(
RoundTallies roundTallies, TallyTransfers tallyTransfers) throws IOException {
RoundTallies roundTallies,
TallyTransfers tallyTransfers,
List<String> candidateOrder) throws IOException {
String outputPath = getOutputFilePathFromInstance("summary");
generateSummarySpreadsheet(roundTallies, null, null, outputPath);
generateSummarySpreadsheet(roundTallies, candidateOrder, null, null, outputPath);
generateSummaryJson(roundTallies, tallyTransfers, null, null, outputPath);
}

Expand Down
5 changes: 3 additions & 2 deletions src/main/java/network/brightspots/rcv/Tabulator.java
Original file line number Diff line number Diff line change
Expand Up @@ -805,8 +805,9 @@ void generateSummaryFiles(String timestamp) throws IOException {
.setSliceIds(sliceIds)
.setRoundToResidualSurplus(roundToResidualSurplus);

writer.generateOverallSummaryFiles(roundTallies, tallyTransfers);
writer.generateBySliceSummaryFiles(roundTalliesBySlices, tallyTransfersBySlice);
List<String> candidateOrder = roundTallies.get(1).getSortedCandidatesByTally();
writer.generateOverallSummaryFiles(roundTallies, tallyTransfers, candidateOrder);
writer.generateBySliceSummaryFiles(roundTalliesBySlices, tallyTransfersBySlice, candidateOrder);

if (config.isGenerateCdfJsonEnabled()) {
try {
Expand Down
1 change: 1 addition & 0 deletions src/test/java/network/brightspots/rcv/TabulatorTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down
Loading
Loading