Skip to content

Commit

Permalink
Have dominion autoload use both names and codes
Browse files Browse the repository at this point in the history
  • Loading branch information
artoonie committed Nov 11, 2024
1 parent 6623238 commit cdf37b3
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 13 deletions.
6 changes: 6 additions & 0 deletions src/main/java/network/brightspots/rcv/BaseCvrReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.List;
import java.util.Map;
import javafx.util.Pair;
import network.brightspots.rcv.RawContestConfig.Candidate;
import network.brightspots.rcv.RawContestConfig.CvrSource;

abstract class BaseCvrReader {
Expand Down Expand Up @@ -70,6 +71,11 @@ public List<String> readCandidateListFromCvr(List<CastVoteRecord> castVoteRecord
return new ArrayList<>();
}

// Allow any reader-specific postprocessing of the candidate list.
public Candidate postprocessAutoloadedCandidate(Candidate candidate) {
return candidate;
}

// Gather candidate names from the CVR that are not in the config.
Map<String, Integer> gatherUnknownCandidates(
List<CastVoteRecord> castVoteRecords, boolean includeCandidatesWithZeroVotes) {
Expand Down
29 changes: 23 additions & 6 deletions src/main/java/network/brightspots/rcv/DominionCvrReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class DominionCvrReader extends BaseCvrReader {
private Map<Integer, String> precinctPortions;
// map of contest ID to Contest data
private Map<String, Contest> contests;
private List<Candidate> candidates;
private HashMap<String, Candidate> candidates;

DominionCvrReader(ContestConfig config, RawContestConfig.CvrSource source) {
super(config, source);
Expand Down Expand Up @@ -100,9 +100,9 @@ private static Map<Integer, String> getPrecinctData(String precinctPath) {
return precinctsById;
}

// returns list of Candidate objects parsed from CandidateManifest.json
private static List<Candidate> getCandidates(String candidatePath) {
ArrayList<Candidate> candidates = new ArrayList<>();
// returns a map of Codes to Candidate objects parsed from CandidateManifest.json
private static HashMap<String, Candidate> getCandidates(String candidatePath) {
HashMap<String, Candidate> candidates = new HashMap<>();
try {
HashMap json = JsonParser.readFromFile(candidatePath, HashMap.class);
ArrayList candidateList = (ArrayList) json.get("List");
Expand All @@ -113,7 +113,7 @@ private static List<Candidate> getCandidates(String candidatePath) {
String code = id.toString();
String contestId = candidateMap.get("ContestId").toString();
Candidate newCandidate = new Candidate(name, code, contestId);
candidates.add(newCandidate);
candidates.put(code, newCandidate);
}
} catch (Exception exception) {
Logger.severe("Error parsing candidate manifest:\n%s", exception);
Expand Down Expand Up @@ -188,7 +188,7 @@ private void validateNamesAreInContest(List<CastVoteRecord> castVoteRecords)
throws CastVoteRecord.CvrParseException {
// build a lookup map to optimize CVR parsing
Map<String, Set<String>> contestIdToCandidateNames = new HashMap<>();
for (Candidate candidate : this.candidates) {
for (Candidate candidate : this.candidates.values()) {
Set<String> candidates;
if (contestIdToCandidateNames.containsKey(candidate.getContestId())) {
candidates = contestIdToCandidateNames.get(candidate.getContestId());
Expand Down Expand Up @@ -226,6 +226,23 @@ private void validateNamesAreInContest(List<CastVoteRecord> castVoteRecords)
}
}

// The autoloaded candidates loads only codes, and sets them as the name.
// Fix these candidates with the correct name, and their code as an alias instead.
public RawContestConfig.Candidate postprocessAutoloadedCandidate(
RawContestConfig.Candidate candidateToFix) {
String candidateCode = candidateToFix.getName();
Candidate loadedCandidate = candidates.get(candidateCode);
RawContestConfig.Candidate fixedCandidate;
if (loadedCandidate != null && loadedCandidate.code.equals(candidateToFix.getName())) {
// The auto-loaded candidate loaded their code as a name. Fix it, and make the code an alias.
fixedCandidate = new RawContestConfig.Candidate(loadedCandidate.name, candidateCode, false);
} else {
fixedCandidate = candidateToFix;
}

return fixedCandidate;
}

// parse the CVR file or files into a List of CastVoteRecords for tabulation
private void gatherCvrsForContest(List<CastVoteRecord> castVoteRecords, String contestIdToLoad) {
try {
Expand Down
19 changes: 12 additions & 7 deletions src/main/java/network/brightspots/rcv/GuiConfigController.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.ResourceBundle;
Expand Down Expand Up @@ -1720,7 +1722,7 @@ protected Void call() {
}
if (cvrsSpecified) {
// Gather unloaded names from each of the sources and place into the HashSet
Set<String> unloadedNames = new HashSet<>();
HashMap<String, Candidate> unloadedNames = new HashMap<>();
for (CvrSource source : sources) {
Provider provider = ContestConfig.getProvider(source);
try {
Expand All @@ -1729,7 +1731,11 @@ protected Void call() {
reader.readCastVoteRecords(castVoteRecords);
Set<String> unknownCandidates = reader.gatherUnknownCandidates(
castVoteRecords, true).keySet();
unloadedNames.addAll(unknownCandidates);
for (String name : unknownCandidates) {
Candidate candidate = new Candidate(name, null, false);
candidate = reader.postprocessAutoloadedCandidate(candidate);
unloadedNames.put(candidate.getName(), candidate);
}
} catch (ContestConfig.UnrecognizedProviderException e) {
Logger.severe(
"Unrecognized provider \"%s\" in source file \"%s\": %s",
Expand All @@ -1743,15 +1749,14 @@ protected Void call() {

// Validate each name and add to the table of candidates
int successCount = 0;
for (String name : unloadedNames) {
Candidate candidate = new Candidate(name, null, false);
for (Map.Entry<String, Candidate> entry : unloadedNames.entrySet()) {
Set<ValidationError> validationErrors =
ContestConfig.performBasicCandidateValidation(candidate);
ContestConfig.performBasicCandidateValidation(entry.getValue());
if (validationErrors.isEmpty()) {
tableViewCandidates.getItems().add(candidate);
tableViewCandidates.getItems().add(entry.getValue());
successCount++;
} else {
Logger.severe("Failed to load candidate \"%s\"!", name);
Logger.severe("Failed to load candidate \"%s\"!", entry.getKey());
}
}

Expand Down

0 comments on commit cdf37b3

Please sign in to comment.