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

Cdf Json Fixes #506

Merged
merged 37 commits into from
Sep 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
427c485
plumbing for xml cdf reading
moldover Jul 31, 2020
36f53cc
add contestId to json and xml CDF parse logic
moldover Aug 3, 2020
687b25b
dont parse Candidate data from CDF at runtime - this enforces the sam…
moldover Aug 3, 2020
4f39417
Handle ContestSelections in XML correctly
moldover Aug 5, 2020
a4cf53d
Merge branch 'develop' into xml_cdf
moldover Aug 23, 2020
6d74890
add NIST example 2 (seems to be correctly formed)
moldover Aug 24, 2020
b071b55
fill out CDF XML class definitions needed for tabulation
moldover Aug 24, 2020
865d104
add some helpers
moldover Aug 24, 2020
f2a7791
cleanup pre-processing code
moldover Aug 24, 2020
e9d9b80
add more class fields around contest selections
moldover Aug 31, 2020
05ff970
handle write-ins
moldover Aug 31, 2020
eae666a
get first 2 Unisyn regression tests working :)
moldover Aug 31, 2020
d504d23
fix first NIST CDF XML test
moldover Aug 31, 2020
2bbfcdb
cleanup test names
moldover Aug 31, 2020
3e1c591
parse GpUnit from CVRs
moldover Aug 31, 2020
598f52a
remove un-used assets
moldover Aug 31, 2020
c5384ce
Merge branch 'develop' into xml_cdf
moldover Aug 31, 2020
a5638ec
updates for PR #504
moldover Sep 2, 2020
c6e0a52
updated data from Unisyn with GpUnit (precinct) parsing validated
moldover Sep 3, 2020
c86c840
add more Unisyn regression tests
moldover Sep 5, 2020
b25d4e3
tabulate all elections - in practice we should not see more than one
moldover Sep 5, 2020
d7bbc3e
updates for PR #504
moldover Sep 5, 2020
0886035
better logging
moldover Sep 12, 2020
c7df230
first cut at fixing JSON CDF reader:
moldover Sep 12, 2020
ea1e69c
update ResultsWriter CVR generation code to create Candidate objects …
moldover Sep 12, 2020
6583599
updates for PR #504
moldover Sep 13, 2020
3d1e7e0
fix typo
moldover Sep 13, 2020
5ab9ac9
update test asset with contest name
moldover Sep 13, 2020
662ee9d
better handling for cdf reader parse errors
moldover Sep 13, 2020
28246f3
Merge branch 'json_cdf' into xml_cdf
moldover Sep 13, 2020
fca0371
update all CDF json assets
moldover Sep 13, 2020
57d5d89
Updates to CDF parsing logic:
moldover Sep 15, 2020
83ba2b1
Merge branch 'develop' into json_cdf
moldover Sep 20, 2020
41fb6e9
cleanup for PR #506
moldover Sep 21, 2020
845baf2
updates for PR #505
moldover Sep 23, 2020
ca15c7a
Merge branch 'develop' into json_cdf
moldover Sep 23, 2020
b656d11
Throw if undeclared write-in is found but label has not been defined.
moldover Sep 24, 2020
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
383 changes: 242 additions & 141 deletions src/main/java/network/brightspots/rcv/CommonDataFormatReader.java

Large diffs are not rendered by default.

49 changes: 32 additions & 17 deletions src/main/java/network/brightspots/rcv/ResultsWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ class ResultsWriter {
private static final String CDF_GPU_ID_FORMAT = "gpu-%d";
private static final String CDF_REPORTING_DEVICE_ID = "rd-001";

private static final Map<String, String> candidateCodeToCdfId = new HashMap<>();
private static final Map<String, String> cdfCandidateCodeToContestSelectionId = new HashMap<>();
private static final Map<String, String> cdfCandidateCodeToCandidateId = new HashMap<>();

// number of rounds needed to elect winner(s)
private int numRounds;
Expand Down Expand Up @@ -136,18 +137,27 @@ private static String generateCvrSnapshotId(String cvrId, Integer round) {
: String.format("ballot-%s", cvrId);
}

private static String getCdfIdForCandidateCode(String code) {
String id = candidateCodeToCdfId.get(code);
// generates an internal ContestSelectionId based on a candidate code
private static String getCdfContestSelectionIdForCandidateCode(String code) {
String id = cdfCandidateCodeToContestSelectionId.get(code);
if (id == null) {
id =
code.startsWith("cs-")
? code
: String.format("cs-%s", sanitizeStringForOutput(code).toLowerCase());
candidateCodeToCdfId.put(code, id);
id = String.format("cs-%s", sanitizeStringForOutput(code).toLowerCase());
cdfCandidateCodeToContestSelectionId.put(code, id);
}
return id;
}

// generates an internal CandidateId based on a candidate code
private static String getCdfCandidateIdForCandidateCode(String code) {
String id = cdfCandidateCodeToCandidateId.get(code);
if (id == null) {
id = String.format("c-%s", sanitizeStringForOutput(code).toLowerCase());
cdfCandidateCodeToCandidateId.put(code, id);
}
return id;
}


// Instead of a map from rank to list of candidates, we need a sorted list of candidates
// with the ranks they were given. (Ordinarily a candidate will have only a single rank, but they
// could have multiple ranks if the ballot duplicates the candidate, i.e. assigns them multiple
Expand Down Expand Up @@ -751,7 +761,7 @@ private Map<String, Object> generateCvrSnapshotMap(

selectionMapList.add(
Map.ofEntries(
entry("ContestSelectionId", getCdfIdForCandidateCode(candidateCode)),
entry("ContestSelectionId", getCdfContestSelectionIdForCandidateCode(candidateCode)),
entry("SelectionPosition", selectionPositionMapList),
entry("@type", "CVR.CVRContestSelection")));
}
Expand All @@ -772,31 +782,36 @@ private Map<String, Object> generateCvrSnapshotMap(
private Map<String, Object> generateCdfMapForElection() {
HashMap<String, Object> electionMap = new HashMap<>();

// containers for election-level data
List<Map<String, Object>> contestSelections = new LinkedList<>();
List<Map<String, Object>> candidates = new LinkedList<>();

// iterate all candidates and create Candidate and ContestSelection objects for them
List<String> candidateCodes = new LinkedList<>(config.getCandidateCodeList());
Collections.sort(candidateCodes);
for (String candidateCode : candidateCodes) {
Map<String, String> codeMap =
candidates.add(
Map.ofEntries(
entry("@type", "CVR.Code"),
entry("Type", "other"),
entry("OtherType", "vendor-label"),
entry("Value", config.getNameForCandidateCode(candidateCode)));
entry("@id", getCdfCandidateIdForCandidateCode(candidateCode)),
entry("Name", candidateCode)));

contestSelections.add(
Map.ofEntries(
entry("@id", getCdfIdForCandidateCode(candidateCode)),
entry("@id", getCdfContestSelectionIdForCandidateCode(candidateCode)),
entry("@type", "CVR.ContestSelection"),
entry("Code", new Map[]{codeMap})));
entry("CandidateIds", new String[]{getCdfCandidateIdForCandidateCode(candidateCode)})));
}

Map<String, Object> contestJson =
Map.ofEntries(
entry("@id", CDF_CONTEST_ID),
entry("@type", "CVR.CandidateContest"),
entry("ContestSelection", contestSelections),
entry("@type", "CVR.CandidateContest"));
entry("Name", config.getContestName())
);

electionMap.put("@id", CDF_ELECTION_ID);
electionMap.put("Candidate", candidates);
electionMap.put("Contest", new Map[]{contestJson});
electionMap.put("ElectionScopeId", CDF_GPU_ID);
electionMap.put("@type", "CVR.Election");
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/network/brightspots/rcv/TabulatorSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -336,8 +336,12 @@ private List<CastVoteRecord> parseCastVoteRecords(ContestConfig config, Set<Stri
encounteredSourceProblem = true;
} catch (CvrParseException e) {
encounteredSourceProblem = true;
} catch (Exception exception) {
Logger.severe("Unexpected error parsing source file: %s\n%s", cvrPath,
exception.toString());
encounteredSourceProblem = true;
}
}
}

if (encounteredSourceProblem) {
Logger.log(Level.SEVERE, "Parsing cast vote records failed!");
Expand Down
14 changes: 11 additions & 3 deletions src/test/java/network/brightspots/rcv/TabulatorTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.io.BufferedReader;
import java.io.File;
Expand Down Expand Up @@ -117,9 +118,10 @@ private static String getTestFilePath(String stem, String suffix) {
// helper function to support running various tabulation tests
private static void runTabulationTest(String stem) {
String configPath = getTestFilePath(stem, "_config.json");
Logger.info("Running tabulation test: %s\nTabulating config file: %s...", stem, configPath);
TabulatorSession session = new TabulatorSession(configPath);
session.tabulate();

Logger.info("Examining tabulation test results...");
String timestampString = session.getTimestampString();
ContestConfig config = ContestConfig.loadContestConfig(configPath);
assertNotNull(config);
Expand All @@ -140,7 +142,6 @@ private static void runTabulationTest(String stem) {
"_contest_" + config.rawConfig.cvrFileSources.get(0).getContestId() + "_expected.csv");
assertTrue(fileCompare(session.getConvertedFilesWritten().get(0), expectedPath));
}

// test passed so cleanup test output folder
File outputFolder = new File(session.getOutputPath());
if (outputFolder.listFiles() != null) {
Expand All @@ -156,6 +157,7 @@ private static void runTabulationTest(String stem) {
}
}
}
Logger.info("Test complete.");
}

private static void compareJsons(
Expand Down Expand Up @@ -183,7 +185,13 @@ private static void compareJson(
+ "_expected_"
+ jsonType
+ ".json");
assertTrue(fileCompare(expectedPath, actualOutputPath));
Logger.info("Comparing files:\nGenerated: %s\nReference: %s", actualOutputPath, expectedPath);
if (fileCompare(expectedPath, actualOutputPath)) {
Logger.info("Files are equal.");
} else {
Logger.info("Files are different.");
fail();
}
}

@BeforeAll
Expand Down
Loading