From 065f217646132381b94a4e3cdd32fe6590efbda7 Mon Sep 17 00:00:00 2001 From: vteague Date: Wed, 18 Sep 2024 09:37:17 +1000 Subject: [PATCH 01/40] Some thinking about where to make the changes. --- .../corla/endpoint/EstimateSampleSizes.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java index 28ca5242..b5544efd 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java @@ -146,6 +146,9 @@ public String estimateSampleSizes() { final List countedCRs = ContestCounter.countAllContests().stream().peek(cr -> cr.setAuditReason(AuditReason.OPPORTUNISTIC_BENEFITS)).toList(); + // FIXME Possibly all we need to do is run through the countedCRs at this point and, if any have + // a zero ballot count, update the ballot count and the diluted margin. + // Try to get the DoS Dashboard, which may contain the risk limit for the audit. final BigDecimal riskLimit = Persistence.getByID(DoSDashboard.ID, DoSDashboard.class).auditInfo().riskLimit(); if (riskLimit == null) { From 59a40281c50a553303afd7387e4e231e9563dd80 Mon Sep 17 00:00:00 2001 From: vteague Date: Mon, 14 Oct 2024 10:21:53 -0600 Subject: [PATCH 02/40] Threaded a boolean for 'use Manifests' through contest counter, so it can be false when we're doing sample size estimation and otherwise always true. --- .../endpoint/AbstractAllIrvEndpoint.java | 2 +- .../corla/endpoint/EstimateSampleSizes.java | 2 +- .../corla/controller/ContestCounter.java | 30 +++++++++++++++---- .../corla/endpoint/StartAuditRound.java | 2 +- .../ComparisonAuditControllerTests.java | 6 ++-- .../endpoint/EstimateSampleSizesTests.java | 4 +-- 6 files changed, 33 insertions(+), 13 deletions(-) diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java index 9bbc27ed..83cbd080 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java @@ -97,7 +97,7 @@ protected static List getIRVContestResults() { final String msg = "Inconsistent contest types:"; // Find all the ContestResults with any that match IRV. - List results = ContestCounter.countAllContests().stream() + List results = ContestCounter.countAllContests(false).stream() .filter(cr -> cr.getContests().stream().map(Contest::description) .anyMatch(d -> d.equalsIgnoreCase(ContestType.IRV.toString()))).toList(); diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java index b5544efd..836d25ea 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java @@ -143,7 +143,7 @@ public String estimateSampleSizes() { // in a ContestResult for an IRV contest will not be used. In the call to ContestCounter // (countAllContests), all persisted CountyContestResults will be accessed from the database, // grouped by contest, and accumulated into a single ContestResult. - final List countedCRs = ContestCounter.countAllContests().stream().peek(cr -> + final List countedCRs = ContestCounter.countAllContests(true).stream().peek(cr -> cr.setAuditReason(AuditReason.OPPORTUNISTIC_BENEFITS)).toList(); // FIXME Possibly all we need to do is run through the countedCRs at this point and, if any have diff --git a/server/eclipse-project/src/main/java/us/freeandfair/corla/controller/ContestCounter.java b/server/eclipse-project/src/main/java/us/freeandfair/corla/controller/ContestCounter.java index 58fe7208..11b8355a 100644 --- a/server/eclipse-project/src/main/java/us/freeandfair/corla/controller/ContestCounter.java +++ b/server/eclipse-project/src/main/java/us/freeandfair/corla/controller/ContestCounter.java @@ -41,14 +41,14 @@ private ContestCounter() { * @return List A high level view of contests and their * participants. */ - public static List countAllContests() { + public static List countAllContests(boolean useManifests) { return Persistence.getAll(CountyContestResult.class) .stream() .collect(Collectors.groupingBy(x -> x.contest().name())) .entrySet() .stream() - .map(ContestCounter::countContest) + .map((Entry> countyContestResults) -> countContest(countyContestResults, useManifests)) .collect(Collectors.toList()); } @@ -85,9 +85,14 @@ public static Set pairwiseMargins(final Set winners, * Set voteTotals on CONTEST based on all counties that have that * Contest name in their uploaded CVRs * Not valid for IRV. + * @param countyContestResults the county-by-county contest results, which are useful for plurality. + * @param useManifests whether to use manifests to compute the total number of ballots. This + * *must* be true when counting for audits - it can be false only when + * doing sample size estimation. In this case, it computes the total number + * of ballots based on the (untrusted) CVRs. **/ - public static ContestResult - countContest(final Map.Entry> countyContestResults) { + public static ContestResult countContest(final Map.Entry> countyContestResults, + boolean useManifests) { final String contestName = countyContestResults.getKey(); final ContestResult contestResult = ContestResultQueries.findOrCreate(contestName); @@ -126,7 +131,12 @@ public static Set pairwiseMargins(final Set winners, .map(cr -> cr.county()) .collect(Collectors.toSet())); - final Long ballotCount = BallotManifestInfoQueries.totalBallots(contestResult.countyIDs()); + Long ballotCount; + if(useManifests) { + ballotCount = BallotManifestInfoQueries.totalBallots(contestResult.countyIDs()); + } else { + ballotCount = countCVRs(contestResult); + } final Set margins = pairwiseMargins(contestResult.getWinners(), contestResult.getLosers(), voteTotals); @@ -148,6 +158,16 @@ public static Set pairwiseMargins(final Set winners, return contestResult; } + /** + * Count the number of ballots by counting the CVRs. Note this should *not* be used for auditing, + * only for sample-size estimation. + * @param contestResult the contestResult for this contest. + * @return the total number of CVRs that contain the contest. + */ + private static Long countCVRs(ContestResult contestResult) { + return -1L; + } + /** add em up **/ public static Map accumulateVoteTotals(final List> voteTotals) { diff --git a/server/eclipse-project/src/main/java/us/freeandfair/corla/endpoint/StartAuditRound.java b/server/eclipse-project/src/main/java/us/freeandfair/corla/endpoint/StartAuditRound.java index 7e016321..99457f6e 100644 --- a/server/eclipse-project/src/main/java/us/freeandfair/corla/endpoint/StartAuditRound.java +++ b/server/eclipse-project/src/main/java/us/freeandfair/corla/endpoint/StartAuditRound.java @@ -256,7 +256,7 @@ public List countAndSaveContests(final Set cta) { LOGGER.debug(String.format("[countAndSaveContests: cta=%s]", cta)); final Map tcr = targetedContestReasons(cta); - return ContestCounter.countAllContests().stream().map(cr -> { + return ContestCounter.countAllContests(false).stream().map(cr -> { cr.setAuditReason(tcr.getOrDefault(cr.getContestName(), AuditReason.OPPORTUNISTIC_BENEFITS)); return cr; diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/controller/ComparisonAuditControllerTests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/controller/ComparisonAuditControllerTests.java index 69bfe642..41828199 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/controller/ComparisonAuditControllerTests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/controller/ComparisonAuditControllerTests.java @@ -100,7 +100,7 @@ public void IRVContestMakesIRVAudit() { testUtils.log(LOGGER, "IRVContestMakesIRVAudit"); // Set up the contest results from the stored data. - List results = ContestCounter.countAllContests(); + List results = ContestCounter.countAllContests(false); // Find all the ContestResults for TinyIRV - there should be one. List tinyIRVResults = results.stream().filter( @@ -121,7 +121,7 @@ public void pluralityContestMakesPluralityAudit() { testUtils.log(LOGGER, "pluralityContestMakesPluralityAudit"); // Set up the contest results from the stored data. - List results = ContestCounter.countAllContests(); + List results = ContestCounter.countAllContests(false); // Find all the ContestResults for TinyPlurality - there should be one. List tinyPluralityResults = results.stream().filter( @@ -142,7 +142,7 @@ public void inconsistentContestThrowsException() { testUtils.log(LOGGER, "inconsistentContestThrowsException"); // Set up the contest results from the stored data. - List results = ContestCounter.countAllContests(); + List results = ContestCounter.countAllContests(false); // Find all the contestResults for TinyMixed - there should be one. List tinyMixedResults = results.stream().filter( diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizesTests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizesTests.java index 341407e1..47fe6435 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizesTests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizesTests.java @@ -154,7 +154,7 @@ void basicEstimatedSampleSizesPluralityAndIRV() { doSD.updateAuditInfo(new AuditInfo(null, null, null,null, BigDecimal.valueOf(0.04))); // Mock return of empty contest list. - mockedCounter.when(ContestCounter::countAllContests).thenReturn(List.of()); + mockedCounter.when(() -> ContestCounter.countAllContests(true)).thenReturn(List.of()); // Check for error response. errorBody = ""; @@ -169,7 +169,7 @@ void basicEstimatedSampleSizesPluralityAndIRV() { // Mock non-empty contest response (one plurality and one IRV contest). List mockedContestResults = List.of(pluralityContestResult, irvContestResult); - mockedCounter.when(ContestCounter::countAllContests).thenReturn(mockedContestResults); + mockedCounter.when(() -> ContestCounter.countAllContests(true)).thenReturn(mockedContestResults); endpoint.endpointBody(request, response); From f1dfa613d9cd699d28f83b5a61d2c901cf51e7f7 Mon Sep 17 00:00:00 2001 From: vteague Date: Mon, 14 Oct 2024 10:26:37 -0600 Subject: [PATCH 03/40] Corrected booleans. --- .../corla/endpoint/AbstractAllIrvEndpoint.java | 2 +- .../corla/endpoint/EstimateSampleSizes.java | 2 +- .../java/us/freeandfair/corla/endpoint/StartAuditRound.java | 2 +- .../corla/controller/ComparisonAuditControllerTests.java | 6 +++--- .../corla/endpoint/EstimateSampleSizesTests.java | 4 ++-- .../corla/endpoint/GenerateAssertionsAPITests.java | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java index 83cbd080..bd0043f0 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java @@ -97,7 +97,7 @@ protected static List getIRVContestResults() { final String msg = "Inconsistent contest types:"; // Find all the ContestResults with any that match IRV. - List results = ContestCounter.countAllContests(false).stream() + List results = ContestCounter.countAllContests(true).stream() .filter(cr -> cr.getContests().stream().map(Contest::description) .anyMatch(d -> d.equalsIgnoreCase(ContestType.IRV.toString()))).toList(); diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java index 836d25ea..048fa259 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java @@ -143,7 +143,7 @@ public String estimateSampleSizes() { // in a ContestResult for an IRV contest will not be used. In the call to ContestCounter // (countAllContests), all persisted CountyContestResults will be accessed from the database, // grouped by contest, and accumulated into a single ContestResult. - final List countedCRs = ContestCounter.countAllContests(true).stream().peek(cr -> + final List countedCRs = ContestCounter.countAllContests(false).stream().peek(cr -> cr.setAuditReason(AuditReason.OPPORTUNISTIC_BENEFITS)).toList(); // FIXME Possibly all we need to do is run through the countedCRs at this point and, if any have diff --git a/server/eclipse-project/src/main/java/us/freeandfair/corla/endpoint/StartAuditRound.java b/server/eclipse-project/src/main/java/us/freeandfair/corla/endpoint/StartAuditRound.java index 99457f6e..1b156124 100644 --- a/server/eclipse-project/src/main/java/us/freeandfair/corla/endpoint/StartAuditRound.java +++ b/server/eclipse-project/src/main/java/us/freeandfair/corla/endpoint/StartAuditRound.java @@ -256,7 +256,7 @@ public List countAndSaveContests(final Set cta) { LOGGER.debug(String.format("[countAndSaveContests: cta=%s]", cta)); final Map tcr = targetedContestReasons(cta); - return ContestCounter.countAllContests(false).stream().map(cr -> { + return ContestCounter.countAllContests(true).stream().map(cr -> { cr.setAuditReason(tcr.getOrDefault(cr.getContestName(), AuditReason.OPPORTUNISTIC_BENEFITS)); return cr; diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/controller/ComparisonAuditControllerTests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/controller/ComparisonAuditControllerTests.java index 41828199..2fec25f4 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/controller/ComparisonAuditControllerTests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/controller/ComparisonAuditControllerTests.java @@ -100,7 +100,7 @@ public void IRVContestMakesIRVAudit() { testUtils.log(LOGGER, "IRVContestMakesIRVAudit"); // Set up the contest results from the stored data. - List results = ContestCounter.countAllContests(false); + List results = ContestCounter.countAllContests(true); // Find all the ContestResults for TinyIRV - there should be one. List tinyIRVResults = results.stream().filter( @@ -121,7 +121,7 @@ public void pluralityContestMakesPluralityAudit() { testUtils.log(LOGGER, "pluralityContestMakesPluralityAudit"); // Set up the contest results from the stored data. - List results = ContestCounter.countAllContests(false); + List results = ContestCounter.countAllContests(true); // Find all the ContestResults for TinyPlurality - there should be one. List tinyPluralityResults = results.stream().filter( @@ -142,7 +142,7 @@ public void inconsistentContestThrowsException() { testUtils.log(LOGGER, "inconsistentContestThrowsException"); // Set up the contest results from the stored data. - List results = ContestCounter.countAllContests(false); + List results = ContestCounter.countAllContests(true); // Find all the contestResults for TinyMixed - there should be one. List tinyMixedResults = results.stream().filter( diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizesTests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizesTests.java index 47fe6435..33b1da13 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizesTests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizesTests.java @@ -154,7 +154,7 @@ void basicEstimatedSampleSizesPluralityAndIRV() { doSD.updateAuditInfo(new AuditInfo(null, null, null,null, BigDecimal.valueOf(0.04))); // Mock return of empty contest list. - mockedCounter.when(() -> ContestCounter.countAllContests(true)).thenReturn(List.of()); + mockedCounter.when(() -> ContestCounter.countAllContests(false)).thenReturn(List.of()); // Check for error response. errorBody = ""; @@ -169,7 +169,7 @@ void basicEstimatedSampleSizesPluralityAndIRV() { // Mock non-empty contest response (one plurality and one IRV contest). List mockedContestResults = List.of(pluralityContestResult, irvContestResult); - mockedCounter.when(() -> ContestCounter.countAllContests(true)).thenReturn(mockedContestResults); + mockedCounter.when(() -> ContestCounter.countAllContests(false)).thenReturn(mockedContestResults); endpoint.endpointBody(request, response); diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/GenerateAssertionsAPITests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/GenerateAssertionsAPITests.java index a8ca6951..187d2e22 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/GenerateAssertionsAPITests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/GenerateAssertionsAPITests.java @@ -202,7 +202,7 @@ void assertionGenerationBlockedWhenInWrongASMState() { // Mock non-empty contest response (one IRV contest). List mockedContestResults = List.of(tinyIRVContestResult); - mockedCounter.when(ContestCounter::countAllContests).thenReturn(mockedContestResults); + mockedCounter.when(() -> ContestCounter.countAllContests(true)).thenReturn(mockedContestResults); // We seem to need a dummy request to run before. final Request request = new SparkRequestStub("", Map.of(CONTEST_NAME, tinyIRV)); From f95fd708673da75dbcfcc00f6378238f3a7e036f Mon Sep 17 00:00:00 2001 From: vteague Date: Wed, 16 Oct 2024 13:30:24 -0600 Subject: [PATCH 04/40] Estimate sample sizes without manifests working. TODO: assertion generation without manifests. --- .../corla/controller/ContestCounter.java | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/server/eclipse-project/src/main/java/us/freeandfair/corla/controller/ContestCounter.java b/server/eclipse-project/src/main/java/us/freeandfair/corla/controller/ContestCounter.java index 11b8355a..6c2807d5 100644 --- a/server/eclipse-project/src/main/java/us/freeandfair/corla/controller/ContestCounter.java +++ b/server/eclipse-project/src/main/java/us/freeandfair/corla/controller/ContestCounter.java @@ -3,24 +3,18 @@ import java.math.BigDecimal; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; +import java.util.*; import java.util.Map.Entry; -import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; -import au.org.democracydevelopers.corla.model.ContestType; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import us.freeandfair.corla.math.Audit; -import us.freeandfair.corla.model.ContestResult; -import us.freeandfair.corla.model.CountyContestResult; +import us.freeandfair.corla.model.*; import us.freeandfair.corla.persistence.Persistence; import us.freeandfair.corla.query.BallotManifestInfoQueries; +import us.freeandfair.corla.query.CastVoteRecordQueries; import us.freeandfair.corla.query.ContestResultQueries; public final class ContestCounter { @@ -151,21 +145,44 @@ public static ContestResult countContest(final Map.Entry Date: Thu, 31 Oct 2024 10:54:15 +1100 Subject: [PATCH 05/40] More efficient getting of IRV contest Results. Omits count of all the plurality contests, and uses manifests if every relevant county has uploaded them, otherwise not. --- .../endpoint/AbstractAllIrvEndpoint.java | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java index bd0043f0..50ede1f3 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java @@ -26,12 +26,18 @@ import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import us.freeandfair.corla.asm.ASMEvent; -import us.freeandfair.corla.controller.ContestCounter; import us.freeandfair.corla.endpoint.AbstractDoSDashboardEndpoint; import us.freeandfair.corla.model.*; +import us.freeandfair.corla.persistence.Persistence; +import us.freeandfair.corla.query.BallotManifestInfoQueries; import java.net.http.HttpClient; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static us.freeandfair.corla.controller.ContestCounter.countContest; /** * An abstract endpoint for communicating with raire. Includes all the information for collecting IRV contests @@ -88,18 +94,45 @@ protected void reset() { } /** - * Get all the ContestResults whose contests are consistently IRV. + * Get (or make) all the ContestResults whose contests are consistently IRV. + * Used for assertion generation and retrieval. + * Uses manifests if they are there, but just counts the CSVs if not. + * This is analogous to (and mostly copied from) ContestCounter::countAllContests, but restricted + * to the IRV ones. Although the countContest function isn't really useful or meaningful for IRV, + * it is called here because it actually does a lot of other useful things, such as setting the + * number of allowed winners and gathering all the results across counties. * @return A list of all ContestResults for IRV contests. * @throws RuntimeException if it encounters contests with a mix of IRV and any other contest type. + * Assumption: Contest names are unique. */ protected static List getIRVContestResults() { final String prefix = "[getIRVContestResults]"; final String msg = "Inconsistent contest types:"; - // Find all the ContestResults with any that match IRV. - List results = ContestCounter.countAllContests(true).stream() - .filter(cr -> cr.getContests().stream().map(Contest::description) - .anyMatch(d -> d.equalsIgnoreCase(ContestType.IRV.toString()))).toList(); + + List results = Persistence.getAll(CountyContestResult.class) + .stream() + // Collect contests by name across counties. + .collect(Collectors.groupingBy(x -> x.contest().name())) + .entrySet() + .stream() + .filter( + // Filter for those with any IRV descriptions (which should be all) + ((Map.Entry> countyContestResults) -> + countyContestResults.getValue().stream().map(ccr -> ccr.contest().description()) + .anyMatch(d -> d.equalsIgnoreCase(ContestType.IRV.toString()))) + ) + // 'Count' them (which actually does plurality counting and sets various useful values + // such as number of winners). + .map((Map.Entry> countyContestResults) -> { + // Use manifests (for the denominator of the diluted margin) if _all_ counties have + // uploaded one. + boolean useManifests = countyContestResults.getValue().stream(). + allMatch(ccr -> BallotManifestInfoQueries.totalBallots(Set.of(ccr.county().id())) > 0); + return countContest(countyContestResults, useManifests); + } + ) + .toList(); // The above should be sufficient, but just in case, check that each contest we found _all_ // matches IRV, and throw a RuntimeException if not. From 150dbc35b84968fd42cb6d877a6be0f5bf621de4 Mon Sep 17 00:00:00 2001 From: vteague Date: Thu, 31 Oct 2024 11:28:32 +1100 Subject: [PATCH 06/40] Fix GenerateAssertionsAPITests.java to mock the right thing (i.e. IRV Contest results only). --- .../corla/endpoint/AbstractAllIrvEndpoint.java | 3 ++- .../corla/endpoint/GenerateAssertionsAPITests.java | 5 ++--- .../au/org/democracydevelopers/corla/workflows/Demo1.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java index 50ede1f3..a38d64b2 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java @@ -135,7 +135,8 @@ protected static List getIRVContestResults() { .toList(); // The above should be sufficient, but just in case, check that each contest we found _all_ - // matches IRV, and throw a RuntimeException if not. + // matches IRV, and throw a RuntimeException if not - one contest must not mix plurality and + // IRV. for (final ContestResult cr : results) { if (cr.getContests().stream().map(Contest::description) .anyMatch(d -> !d.equalsIgnoreCase(ContestType.IRV.toString()))) { diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/GenerateAssertionsAPITests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/GenerateAssertionsAPITests.java index 187d2e22..ffa08f8b 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/GenerateAssertionsAPITests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/GenerateAssertionsAPITests.java @@ -43,7 +43,6 @@ import spark.Request; import us.freeandfair.corla.Main; import us.freeandfair.corla.asm.*; -import us.freeandfair.corla.controller.ContestCounter; import us.freeandfair.corla.model.AuditReason; import us.freeandfair.corla.model.Choice; import us.freeandfair.corla.model.ContestResult; @@ -192,7 +191,7 @@ void assertionGenerationBlockedWhenInWrongASMState() { // Mock the main class; mock its auth as the mocked state admin auth. try (MockedStatic
mockedMain = Mockito.mockStatic(Main.class); - MockedStatic mockedCounter = Mockito.mockStatic(ContestCounter.class)) { + MockedStatic mockedIRVEndpoint = Mockito.mockStatic(AbstractAllIrvEndpoint.class)) { // Mock auth. mockedMain.when(Main::authentication).thenReturn(auth); @@ -202,7 +201,7 @@ void assertionGenerationBlockedWhenInWrongASMState() { // Mock non-empty contest response (one IRV contest). List mockedContestResults = List.of(tinyIRVContestResult); - mockedCounter.when(() -> ContestCounter.countAllContests(true)).thenReturn(mockedContestResults); + mockedIRVEndpoint.when(AbstractAllIrvEndpoint::getIRVContestResults).thenReturn(mockedContestResults); // We seem to need a dummy request to run before. final Request request = new SparkRequestStub("", Map.of(CONTEST_NAME, tinyIRV)); diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java index f484ee37..f6b3ad1b 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java @@ -30,7 +30,7 @@ /** * A demonstration workflow that uploads CVRs and ballot manifests for all 64 counties. */ -@Test(enabled=true) +@Test(enabled=false) public class Demo1 extends Workflow { /** From 1a312732d9d34c2c084d952999285fc66f7a0006 Mon Sep 17 00:00:00 2001 From: vteague Date: Wed, 18 Sep 2024 09:37:17 +1000 Subject: [PATCH 07/40] Some thinking about where to make the changes. --- .../corla/endpoint/EstimateSampleSizes.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java index 28ca5242..b5544efd 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java @@ -146,6 +146,9 @@ public String estimateSampleSizes() { final List countedCRs = ContestCounter.countAllContests().stream().peek(cr -> cr.setAuditReason(AuditReason.OPPORTUNISTIC_BENEFITS)).toList(); + // FIXME Possibly all we need to do is run through the countedCRs at this point and, if any have + // a zero ballot count, update the ballot count and the diluted margin. + // Try to get the DoS Dashboard, which may contain the risk limit for the audit. final BigDecimal riskLimit = Persistence.getByID(DoSDashboard.ID, DoSDashboard.class).auditInfo().riskLimit(); if (riskLimit == null) { From 9176de0eb49b8c9a2fcc6db6a709eddf0bca199d Mon Sep 17 00:00:00 2001 From: vteague Date: Mon, 14 Oct 2024 10:21:53 -0600 Subject: [PATCH 08/40] Threaded a boolean for 'use Manifests' through contest counter, so it can be false when we're doing sample size estimation and otherwise always true. --- .../endpoint/AbstractAllIrvEndpoint.java | 2 +- .../corla/endpoint/EstimateSampleSizes.java | 2 +- .../corla/controller/ContestCounter.java | 30 +++++++++++++++---- .../corla/endpoint/StartAuditRound.java | 2 +- .../ComparisonAuditControllerTests.java | 6 ++-- .../endpoint/EstimateSampleSizesTests.java | 4 +-- 6 files changed, 33 insertions(+), 13 deletions(-) diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java index 9bbc27ed..83cbd080 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java @@ -97,7 +97,7 @@ protected static List getIRVContestResults() { final String msg = "Inconsistent contest types:"; // Find all the ContestResults with any that match IRV. - List results = ContestCounter.countAllContests().stream() + List results = ContestCounter.countAllContests(false).stream() .filter(cr -> cr.getContests().stream().map(Contest::description) .anyMatch(d -> d.equalsIgnoreCase(ContestType.IRV.toString()))).toList(); diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java index b5544efd..836d25ea 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java @@ -143,7 +143,7 @@ public String estimateSampleSizes() { // in a ContestResult for an IRV contest will not be used. In the call to ContestCounter // (countAllContests), all persisted CountyContestResults will be accessed from the database, // grouped by contest, and accumulated into a single ContestResult. - final List countedCRs = ContestCounter.countAllContests().stream().peek(cr -> + final List countedCRs = ContestCounter.countAllContests(true).stream().peek(cr -> cr.setAuditReason(AuditReason.OPPORTUNISTIC_BENEFITS)).toList(); // FIXME Possibly all we need to do is run through the countedCRs at this point and, if any have diff --git a/server/eclipse-project/src/main/java/us/freeandfair/corla/controller/ContestCounter.java b/server/eclipse-project/src/main/java/us/freeandfair/corla/controller/ContestCounter.java index 58fe7208..11b8355a 100644 --- a/server/eclipse-project/src/main/java/us/freeandfair/corla/controller/ContestCounter.java +++ b/server/eclipse-project/src/main/java/us/freeandfair/corla/controller/ContestCounter.java @@ -41,14 +41,14 @@ private ContestCounter() { * @return List A high level view of contests and their * participants. */ - public static List countAllContests() { + public static List countAllContests(boolean useManifests) { return Persistence.getAll(CountyContestResult.class) .stream() .collect(Collectors.groupingBy(x -> x.contest().name())) .entrySet() .stream() - .map(ContestCounter::countContest) + .map((Entry> countyContestResults) -> countContest(countyContestResults, useManifests)) .collect(Collectors.toList()); } @@ -85,9 +85,14 @@ public static Set pairwiseMargins(final Set winners, * Set voteTotals on CONTEST based on all counties that have that * Contest name in their uploaded CVRs * Not valid for IRV. + * @param countyContestResults the county-by-county contest results, which are useful for plurality. + * @param useManifests whether to use manifests to compute the total number of ballots. This + * *must* be true when counting for audits - it can be false only when + * doing sample size estimation. In this case, it computes the total number + * of ballots based on the (untrusted) CVRs. **/ - public static ContestResult - countContest(final Map.Entry> countyContestResults) { + public static ContestResult countContest(final Map.Entry> countyContestResults, + boolean useManifests) { final String contestName = countyContestResults.getKey(); final ContestResult contestResult = ContestResultQueries.findOrCreate(contestName); @@ -126,7 +131,12 @@ public static Set pairwiseMargins(final Set winners, .map(cr -> cr.county()) .collect(Collectors.toSet())); - final Long ballotCount = BallotManifestInfoQueries.totalBallots(contestResult.countyIDs()); + Long ballotCount; + if(useManifests) { + ballotCount = BallotManifestInfoQueries.totalBallots(contestResult.countyIDs()); + } else { + ballotCount = countCVRs(contestResult); + } final Set margins = pairwiseMargins(contestResult.getWinners(), contestResult.getLosers(), voteTotals); @@ -148,6 +158,16 @@ public static Set pairwiseMargins(final Set winners, return contestResult; } + /** + * Count the number of ballots by counting the CVRs. Note this should *not* be used for auditing, + * only for sample-size estimation. + * @param contestResult the contestResult for this contest. + * @return the total number of CVRs that contain the contest. + */ + private static Long countCVRs(ContestResult contestResult) { + return -1L; + } + /** add em up **/ public static Map accumulateVoteTotals(final List> voteTotals) { diff --git a/server/eclipse-project/src/main/java/us/freeandfair/corla/endpoint/StartAuditRound.java b/server/eclipse-project/src/main/java/us/freeandfair/corla/endpoint/StartAuditRound.java index 7e016321..99457f6e 100644 --- a/server/eclipse-project/src/main/java/us/freeandfair/corla/endpoint/StartAuditRound.java +++ b/server/eclipse-project/src/main/java/us/freeandfair/corla/endpoint/StartAuditRound.java @@ -256,7 +256,7 @@ public List countAndSaveContests(final Set cta) { LOGGER.debug(String.format("[countAndSaveContests: cta=%s]", cta)); final Map tcr = targetedContestReasons(cta); - return ContestCounter.countAllContests().stream().map(cr -> { + return ContestCounter.countAllContests(false).stream().map(cr -> { cr.setAuditReason(tcr.getOrDefault(cr.getContestName(), AuditReason.OPPORTUNISTIC_BENEFITS)); return cr; diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/controller/ComparisonAuditControllerTests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/controller/ComparisonAuditControllerTests.java index 69bfe642..41828199 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/controller/ComparisonAuditControllerTests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/controller/ComparisonAuditControllerTests.java @@ -100,7 +100,7 @@ public void IRVContestMakesIRVAudit() { testUtils.log(LOGGER, "IRVContestMakesIRVAudit"); // Set up the contest results from the stored data. - List results = ContestCounter.countAllContests(); + List results = ContestCounter.countAllContests(false); // Find all the ContestResults for TinyIRV - there should be one. List tinyIRVResults = results.stream().filter( @@ -121,7 +121,7 @@ public void pluralityContestMakesPluralityAudit() { testUtils.log(LOGGER, "pluralityContestMakesPluralityAudit"); // Set up the contest results from the stored data. - List results = ContestCounter.countAllContests(); + List results = ContestCounter.countAllContests(false); // Find all the ContestResults for TinyPlurality - there should be one. List tinyPluralityResults = results.stream().filter( @@ -142,7 +142,7 @@ public void inconsistentContestThrowsException() { testUtils.log(LOGGER, "inconsistentContestThrowsException"); // Set up the contest results from the stored data. - List results = ContestCounter.countAllContests(); + List results = ContestCounter.countAllContests(false); // Find all the contestResults for TinyMixed - there should be one. List tinyMixedResults = results.stream().filter( diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizesTests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizesTests.java index 341407e1..47fe6435 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizesTests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizesTests.java @@ -154,7 +154,7 @@ void basicEstimatedSampleSizesPluralityAndIRV() { doSD.updateAuditInfo(new AuditInfo(null, null, null,null, BigDecimal.valueOf(0.04))); // Mock return of empty contest list. - mockedCounter.when(ContestCounter::countAllContests).thenReturn(List.of()); + mockedCounter.when(() -> ContestCounter.countAllContests(true)).thenReturn(List.of()); // Check for error response. errorBody = ""; @@ -169,7 +169,7 @@ void basicEstimatedSampleSizesPluralityAndIRV() { // Mock non-empty contest response (one plurality and one IRV contest). List mockedContestResults = List.of(pluralityContestResult, irvContestResult); - mockedCounter.when(ContestCounter::countAllContests).thenReturn(mockedContestResults); + mockedCounter.when(() -> ContestCounter.countAllContests(true)).thenReturn(mockedContestResults); endpoint.endpointBody(request, response); From 6a5ddd9a1cfe452ce629711838a5f49115e957cd Mon Sep 17 00:00:00 2001 From: vteague Date: Mon, 14 Oct 2024 10:26:37 -0600 Subject: [PATCH 09/40] Corrected booleans. --- .../corla/endpoint/AbstractAllIrvEndpoint.java | 2 +- .../corla/endpoint/EstimateSampleSizes.java | 2 +- .../java/us/freeandfair/corla/endpoint/StartAuditRound.java | 2 +- .../corla/controller/ComparisonAuditControllerTests.java | 6 +++--- .../corla/endpoint/EstimateSampleSizesTests.java | 4 ++-- .../corla/endpoint/GenerateAssertionsAPITests.java | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java index 83cbd080..bd0043f0 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java @@ -97,7 +97,7 @@ protected static List getIRVContestResults() { final String msg = "Inconsistent contest types:"; // Find all the ContestResults with any that match IRV. - List results = ContestCounter.countAllContests(false).stream() + List results = ContestCounter.countAllContests(true).stream() .filter(cr -> cr.getContests().stream().map(Contest::description) .anyMatch(d -> d.equalsIgnoreCase(ContestType.IRV.toString()))).toList(); diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java index 836d25ea..048fa259 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java @@ -143,7 +143,7 @@ public String estimateSampleSizes() { // in a ContestResult for an IRV contest will not be used. In the call to ContestCounter // (countAllContests), all persisted CountyContestResults will be accessed from the database, // grouped by contest, and accumulated into a single ContestResult. - final List countedCRs = ContestCounter.countAllContests(true).stream().peek(cr -> + final List countedCRs = ContestCounter.countAllContests(false).stream().peek(cr -> cr.setAuditReason(AuditReason.OPPORTUNISTIC_BENEFITS)).toList(); // FIXME Possibly all we need to do is run through the countedCRs at this point and, if any have diff --git a/server/eclipse-project/src/main/java/us/freeandfair/corla/endpoint/StartAuditRound.java b/server/eclipse-project/src/main/java/us/freeandfair/corla/endpoint/StartAuditRound.java index 99457f6e..1b156124 100644 --- a/server/eclipse-project/src/main/java/us/freeandfair/corla/endpoint/StartAuditRound.java +++ b/server/eclipse-project/src/main/java/us/freeandfair/corla/endpoint/StartAuditRound.java @@ -256,7 +256,7 @@ public List countAndSaveContests(final Set cta) { LOGGER.debug(String.format("[countAndSaveContests: cta=%s]", cta)); final Map tcr = targetedContestReasons(cta); - return ContestCounter.countAllContests(false).stream().map(cr -> { + return ContestCounter.countAllContests(true).stream().map(cr -> { cr.setAuditReason(tcr.getOrDefault(cr.getContestName(), AuditReason.OPPORTUNISTIC_BENEFITS)); return cr; diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/controller/ComparisonAuditControllerTests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/controller/ComparisonAuditControllerTests.java index 41828199..2fec25f4 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/controller/ComparisonAuditControllerTests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/controller/ComparisonAuditControllerTests.java @@ -100,7 +100,7 @@ public void IRVContestMakesIRVAudit() { testUtils.log(LOGGER, "IRVContestMakesIRVAudit"); // Set up the contest results from the stored data. - List results = ContestCounter.countAllContests(false); + List results = ContestCounter.countAllContests(true); // Find all the ContestResults for TinyIRV - there should be one. List tinyIRVResults = results.stream().filter( @@ -121,7 +121,7 @@ public void pluralityContestMakesPluralityAudit() { testUtils.log(LOGGER, "pluralityContestMakesPluralityAudit"); // Set up the contest results from the stored data. - List results = ContestCounter.countAllContests(false); + List results = ContestCounter.countAllContests(true); // Find all the ContestResults for TinyPlurality - there should be one. List tinyPluralityResults = results.stream().filter( @@ -142,7 +142,7 @@ public void inconsistentContestThrowsException() { testUtils.log(LOGGER, "inconsistentContestThrowsException"); // Set up the contest results from the stored data. - List results = ContestCounter.countAllContests(false); + List results = ContestCounter.countAllContests(true); // Find all the contestResults for TinyMixed - there should be one. List tinyMixedResults = results.stream().filter( diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizesTests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizesTests.java index 47fe6435..33b1da13 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizesTests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizesTests.java @@ -154,7 +154,7 @@ void basicEstimatedSampleSizesPluralityAndIRV() { doSD.updateAuditInfo(new AuditInfo(null, null, null,null, BigDecimal.valueOf(0.04))); // Mock return of empty contest list. - mockedCounter.when(() -> ContestCounter.countAllContests(true)).thenReturn(List.of()); + mockedCounter.when(() -> ContestCounter.countAllContests(false)).thenReturn(List.of()); // Check for error response. errorBody = ""; @@ -169,7 +169,7 @@ void basicEstimatedSampleSizesPluralityAndIRV() { // Mock non-empty contest response (one plurality and one IRV contest). List mockedContestResults = List.of(pluralityContestResult, irvContestResult); - mockedCounter.when(() -> ContestCounter.countAllContests(true)).thenReturn(mockedContestResults); + mockedCounter.when(() -> ContestCounter.countAllContests(false)).thenReturn(mockedContestResults); endpoint.endpointBody(request, response); diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/GenerateAssertionsAPITests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/GenerateAssertionsAPITests.java index a8ca6951..187d2e22 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/GenerateAssertionsAPITests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/GenerateAssertionsAPITests.java @@ -202,7 +202,7 @@ void assertionGenerationBlockedWhenInWrongASMState() { // Mock non-empty contest response (one IRV contest). List mockedContestResults = List.of(tinyIRVContestResult); - mockedCounter.when(ContestCounter::countAllContests).thenReturn(mockedContestResults); + mockedCounter.when(() -> ContestCounter.countAllContests(true)).thenReturn(mockedContestResults); // We seem to need a dummy request to run before. final Request request = new SparkRequestStub("", Map.of(CONTEST_NAME, tinyIRV)); From 8e0253b187c17854dd32ef4df59d348dc30dc1c2 Mon Sep 17 00:00:00 2001 From: vteague Date: Wed, 16 Oct 2024 13:30:24 -0600 Subject: [PATCH 10/40] Estimate sample sizes without manifests working. TODO: assertion generation without manifests. --- .../corla/controller/ContestCounter.java | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/server/eclipse-project/src/main/java/us/freeandfair/corla/controller/ContestCounter.java b/server/eclipse-project/src/main/java/us/freeandfair/corla/controller/ContestCounter.java index 11b8355a..6c2807d5 100644 --- a/server/eclipse-project/src/main/java/us/freeandfair/corla/controller/ContestCounter.java +++ b/server/eclipse-project/src/main/java/us/freeandfair/corla/controller/ContestCounter.java @@ -3,24 +3,18 @@ import java.math.BigDecimal; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; +import java.util.*; import java.util.Map.Entry; -import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; -import au.org.democracydevelopers.corla.model.ContestType; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import us.freeandfair.corla.math.Audit; -import us.freeandfair.corla.model.ContestResult; -import us.freeandfair.corla.model.CountyContestResult; +import us.freeandfair.corla.model.*; import us.freeandfair.corla.persistence.Persistence; import us.freeandfair.corla.query.BallotManifestInfoQueries; +import us.freeandfair.corla.query.CastVoteRecordQueries; import us.freeandfair.corla.query.ContestResultQueries; public final class ContestCounter { @@ -151,21 +145,44 @@ public static ContestResult countContest(final Map.Entry Date: Thu, 31 Oct 2024 10:54:15 +1100 Subject: [PATCH 11/40] More efficient getting of IRV contest Results. Omits count of all the plurality contests, and uses manifests if every relevant county has uploaded them, otherwise not. --- .../endpoint/AbstractAllIrvEndpoint.java | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java index bd0043f0..50ede1f3 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java @@ -26,12 +26,18 @@ import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import us.freeandfair.corla.asm.ASMEvent; -import us.freeandfair.corla.controller.ContestCounter; import us.freeandfair.corla.endpoint.AbstractDoSDashboardEndpoint; import us.freeandfair.corla.model.*; +import us.freeandfair.corla.persistence.Persistence; +import us.freeandfair.corla.query.BallotManifestInfoQueries; import java.net.http.HttpClient; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static us.freeandfair.corla.controller.ContestCounter.countContest; /** * An abstract endpoint for communicating with raire. Includes all the information for collecting IRV contests @@ -88,18 +94,45 @@ protected void reset() { } /** - * Get all the ContestResults whose contests are consistently IRV. + * Get (or make) all the ContestResults whose contests are consistently IRV. + * Used for assertion generation and retrieval. + * Uses manifests if they are there, but just counts the CSVs if not. + * This is analogous to (and mostly copied from) ContestCounter::countAllContests, but restricted + * to the IRV ones. Although the countContest function isn't really useful or meaningful for IRV, + * it is called here because it actually does a lot of other useful things, such as setting the + * number of allowed winners and gathering all the results across counties. * @return A list of all ContestResults for IRV contests. * @throws RuntimeException if it encounters contests with a mix of IRV and any other contest type. + * Assumption: Contest names are unique. */ protected static List getIRVContestResults() { final String prefix = "[getIRVContestResults]"; final String msg = "Inconsistent contest types:"; - // Find all the ContestResults with any that match IRV. - List results = ContestCounter.countAllContests(true).stream() - .filter(cr -> cr.getContests().stream().map(Contest::description) - .anyMatch(d -> d.equalsIgnoreCase(ContestType.IRV.toString()))).toList(); + + List results = Persistence.getAll(CountyContestResult.class) + .stream() + // Collect contests by name across counties. + .collect(Collectors.groupingBy(x -> x.contest().name())) + .entrySet() + .stream() + .filter( + // Filter for those with any IRV descriptions (which should be all) + ((Map.Entry> countyContestResults) -> + countyContestResults.getValue().stream().map(ccr -> ccr.contest().description()) + .anyMatch(d -> d.equalsIgnoreCase(ContestType.IRV.toString()))) + ) + // 'Count' them (which actually does plurality counting and sets various useful values + // such as number of winners). + .map((Map.Entry> countyContestResults) -> { + // Use manifests (for the denominator of the diluted margin) if _all_ counties have + // uploaded one. + boolean useManifests = countyContestResults.getValue().stream(). + allMatch(ccr -> BallotManifestInfoQueries.totalBallots(Set.of(ccr.county().id())) > 0); + return countContest(countyContestResults, useManifests); + } + ) + .toList(); // The above should be sufficient, but just in case, check that each contest we found _all_ // matches IRV, and throw a RuntimeException if not. From f265e3121eeefe680c4c03572735e77529038744 Mon Sep 17 00:00:00 2001 From: vteague Date: Thu, 31 Oct 2024 11:28:32 +1100 Subject: [PATCH 12/40] Fix GenerateAssertionsAPITests.java to mock the right thing (i.e. IRV Contest results only). --- .../corla/endpoint/AbstractAllIrvEndpoint.java | 3 ++- .../corla/endpoint/GenerateAssertionsAPITests.java | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java index 50ede1f3..a38d64b2 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java @@ -135,7 +135,8 @@ protected static List getIRVContestResults() { .toList(); // The above should be sufficient, but just in case, check that each contest we found _all_ - // matches IRV, and throw a RuntimeException if not. + // matches IRV, and throw a RuntimeException if not - one contest must not mix plurality and + // IRV. for (final ContestResult cr : results) { if (cr.getContests().stream().map(Contest::description) .anyMatch(d -> !d.equalsIgnoreCase(ContestType.IRV.toString()))) { diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/GenerateAssertionsAPITests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/GenerateAssertionsAPITests.java index 187d2e22..ffa08f8b 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/GenerateAssertionsAPITests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/endpoint/GenerateAssertionsAPITests.java @@ -43,7 +43,6 @@ import spark.Request; import us.freeandfair.corla.Main; import us.freeandfair.corla.asm.*; -import us.freeandfair.corla.controller.ContestCounter; import us.freeandfair.corla.model.AuditReason; import us.freeandfair.corla.model.Choice; import us.freeandfair.corla.model.ContestResult; @@ -192,7 +191,7 @@ void assertionGenerationBlockedWhenInWrongASMState() { // Mock the main class; mock its auth as the mocked state admin auth. try (MockedStatic
mockedMain = Mockito.mockStatic(Main.class); - MockedStatic mockedCounter = Mockito.mockStatic(ContestCounter.class)) { + MockedStatic mockedIRVEndpoint = Mockito.mockStatic(AbstractAllIrvEndpoint.class)) { // Mock auth. mockedMain.when(Main::authentication).thenReturn(auth); @@ -202,7 +201,7 @@ void assertionGenerationBlockedWhenInWrongASMState() { // Mock non-empty contest response (one IRV contest). List mockedContestResults = List.of(tinyIRVContestResult); - mockedCounter.when(() -> ContestCounter.countAllContests(true)).thenReturn(mockedContestResults); + mockedIRVEndpoint.when(AbstractAllIrvEndpoint::getIRVContestResults).thenReturn(mockedContestResults); // We seem to need a dummy request to run before. final Request request = new SparkRequestStub("", Map.of(CONTEST_NAME, tinyIRV)); From 672039195ae9bbee9de7ce4d4f91bbe1f5cc5227 Mon Sep 17 00:00:00 2001 From: vteague Date: Sun, 3 Nov 2024 12:53:57 +1100 Subject: [PATCH 13/40] Added some manifests with different numbers of ballots from the CVR files. --- .../corla/endpoint/EstimateSampleSizes.java | 3 - .../EstimateSampleSizesVaryingManifests.java | 91 +++++++++++++++++++ ...hreeCandidatesTenVotes_DoubledManifest.csv | 2 + ...andidatesTenVotes_InsufficientManifest.csv | 2 + ...CandidatesTenVotes_OneAndAHalfManifest.csv | 2 + 5 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java create mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_DoubledManifest.csv create mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_InsufficientManifest.csv create mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_OneAndAHalfManifest.csv diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java index 048fa259..7eaac5df 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java @@ -146,9 +146,6 @@ public String estimateSampleSizes() { final List countedCRs = ContestCounter.countAllContests(false).stream().peek(cr -> cr.setAuditReason(AuditReason.OPPORTUNISTIC_BENEFITS)).toList(); - // FIXME Possibly all we need to do is run through the countedCRs at this point and, if any have - // a zero ballot count, update the ballot count and the diluted margin. - // Try to get the DoS Dashboard, which may contain the risk limit for the audit. final BigDecimal riskLimit = Persistence.getByID(DoSDashboard.ID, DoSDashboard.class).auditInfo().riskLimit(); if (riskLimit == null) { diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java new file mode 100644 index 00000000..c91c661f --- /dev/null +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java @@ -0,0 +1,91 @@ +/* +Democracy Developers IRV extensions to colorado-rla. + +@copyright 2024 Colorado Department of State + +These IRV extensions are designed to connect to a running instance of the raire +service (https://github.com/DemocracyDevelopers/raire-service), in order to +generate assertions that can be audited using colorado-rla. + +The colorado-rla IRV extensions are free software: you can redistribute it and/or modify it under the terms +of the GNU Affero General Public License as published by the Free Software Foundation, either +version 3 of the License, or (at your option) any later version. + +The colorado-rla IRV extensions are distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +See the GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with +raire-service. If not, see . +*/ + +package au.org.democracydevelopers.corla.workflows; + +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * A demonstration workflow that tests sample size estimation with and without manifests, comparing + * the implications and ensuring that manifests are either properly ignored or properly incorporated + * throughout both sample size estimation and subsequent auditing. For example, when the manifest + * exactly matches the CVR count, the samples should be the same; extras in the manifest should + * increase the sample size, and a smaller manifest than CVR list should throw an error. + * All the tests here a the same plain plurality contest - we just vary the absence of presence of + * the manifest, and (if present) the number of ballots it states. + * This assumes that main is running. + */ +@Test(enabled=true) +public class EstimateSampleSizesVaryingManifests extends Workflow { + + /** + * Path for all the data files. + */ + private static final String dataPath = "src/test/resources/CSVs/"; + + /** + * Class-wide logger + */ + private static final Logger LOGGER = LogManager.getLogger(EstimateSampleSizesVaryingManifests.class); + + /** + * This "test" uploads CVRs and ballot manifests for all 64 counties. + */ + @Test(enabled = false) + public void runDemo1(){ + List CVRS = new ArrayList<>(); + + CVRS.add(dataPath + "Demo1/1-adams-cvrexport-plusByron-1.csv"); + CVRS.add(dataPath + "Demo1/2-alamosa-cvrexport-plusByron-2.csv"); + CVRS.add(dataPath + "Demo1/3-arapahoe-Byron-3-plus-tied-irv.csv"); + CVRS.add(dataPath + "Demo1/4-archuleta-kempsey-plusByron-4.csv"); + CVRS.add(dataPath + "split-Byron/Byron-5.csv"); + CVRS.add(dataPath + "split-Byron/Byron-5.csv"); + CVRS.add(dataPath + "split-Byron/Byron-6.csv"); + CVRS.add(dataPath + "Demo1/7-boulder-2023-plusByron-7.csv"); + + List MANIFESTS = new ArrayList<>(); + + MANIFESTS.add(dataPath + "Demo1/1-adams-plusByron-1-manifest.csv"); + MANIFESTS.add(dataPath + "Demo1/2-alamosa-plusByron-2-manifest.csv"); + MANIFESTS.add(dataPath + "split-Byron/Byron-3-manifest.csv"); + MANIFESTS.add(dataPath + "NewSouthWales21/Kempsey_Mayoral.manifest.csv"); + MANIFESTS.add(dataPath + "split-Byron/Byron-5-manifest.csv"); + MANIFESTS.add(dataPath + "split-Byron/Byron-6-manifest.csv"); + MANIFESTS.add(dataPath + "Boulder23/Boulder-IRV-Manifest.csv"); + + for(int i = 8; i < 65; ++i){ + CVRS.add(dataPath + "split-Byron/Byron-" + i + ".csv"); + MANIFESTS.add(dataPath + "split-Byron/Byron-" + i + "-manifest.csv"); + } + + for(int i = 1; i < 65; ++i){ + uploadCounty(i, "cvr-export", CVRS.get(i-1), CVRS.get(i-1) + ".sha256sum"); + uploadCounty(i, "ballot-manifest", MANIFESTS.get(i-1), MANIFESTS.get(i-1) + ".sha256sum"); + } + } + +} diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_DoubledManifest.csv b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_DoubledManifest.csv new file mode 100644 index 00000000..1c2f4c4d --- /dev/null +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_DoubledManifest.csv @@ -0,0 +1,2 @@ +CountyID,ScannerID,BatchID,NumBallots,StorageLocation +TestCounty,1,1,20,Bin 1 diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_InsufficientManifest.csv b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_InsufficientManifest.csv new file mode 100644 index 00000000..e2446120 --- /dev/null +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_InsufficientManifest.csv @@ -0,0 +1,2 @@ +CountyID,ScannerID,BatchID,NumBallots,StorageLocation +TestCounty,1,1,5,Bin 1 diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_OneAndAHalfManifest.csv b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_OneAndAHalfManifest.csv new file mode 100644 index 00000000..3d759935 --- /dev/null +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_OneAndAHalfManifest.csv @@ -0,0 +1,2 @@ +CountyID,ScannerID,BatchID,NumBallots,StorageLocation +TestCounty,1,1,15,Bin 1 From 8d6881f1eea5dd5ece1a550c9cd07b4d606cb987 Mon Sep 17 00:00:00 2001 From: vteague Date: Sun, 3 Nov 2024 18:02:24 +1100 Subject: [PATCH 14/40] Stub for getting data for sample size estimates. --- .../corla/endpoint/EstimateSampleSizes.java | 2 +- .../corla/workflows/Demo1.java | 3 ++ .../EstimateSampleSizesVaryingManifests.java | 37 ++++++------------- .../corla/workflows/Workflow.java | 7 ++++ 4 files changed, 22 insertions(+), 27 deletions(-) diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java index 7eaac5df..8a88ca64 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java @@ -186,7 +186,7 @@ public String estimateSampleSizes() { * @param dilutedMargin The margin divided by the total Auditable ballots. * @param estimatedSamples The estimated samples to audit. */ - private record EstimateData(String countyName, String contestName, String contestType, + public record EstimateData(String countyName, String contestName, String contestType, int contestBallots, long totalBallots, BigDecimal dilutedMargin, int estimatedSamples) implements Comparable { diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java index c6a7d2bb..41d88160 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java @@ -46,6 +46,9 @@ public class Demo1 extends Workflow { /** * This "test" uploads CVRs and ballot manifests for all 64 counties. + * The uploads match the following http files in the workflows directory: + * - demo1_loadCVRs, demo1_loadManifests, + * - Boulder_loadCVRs, Boulder_loadManifest. */ @Test(enabled = false) public void runDemo1(){ diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java index c91c661f..2a896952 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java @@ -21,6 +21,7 @@ package au.org.democracydevelopers.corla.workflows; +import au.org.democracydevelopers.corla.endpoint.EstimateSampleSizes; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.testng.annotations.Test; @@ -52,40 +53,24 @@ public class EstimateSampleSizesVaryingManifests extends Workflow { private static final Logger LOGGER = LogManager.getLogger(EstimateSampleSizesVaryingManifests.class); /** - * This "test" uploads CVRs and ballot manifests for all 64 counties. + * This "test" uploads CVRs and ballot manifests. */ @Test(enabled = false) - public void runDemo1(){ - List CVRS = new ArrayList<>(); + public void runManifestVaryingDemo(){ - CVRS.add(dataPath + "Demo1/1-adams-cvrexport-plusByron-1.csv"); - CVRS.add(dataPath + "Demo1/2-alamosa-cvrexport-plusByron-2.csv"); - CVRS.add(dataPath + "Demo1/3-arapahoe-Byron-3-plus-tied-irv.csv"); - CVRS.add(dataPath + "Demo1/4-archuleta-kempsey-plusByron-4.csv"); - CVRS.add(dataPath + "split-Byron/Byron-5.csv"); - CVRS.add(dataPath + "split-Byron/Byron-5.csv"); - CVRS.add(dataPath + "split-Byron/Byron-6.csv"); - CVRS.add(dataPath + "Demo1/7-boulder-2023-plusByron-7.csv"); + List CVRS = new ArrayList<>(); + CVRS.add(dataPath + "PluralityOnly/Plurality1And2.csv"); List MANIFESTS = new ArrayList<>(); + MANIFESTS.add(dataPath + "PluralityOnly/ThreeCandidatesTenVotes_Manifest.csv"); - MANIFESTS.add(dataPath + "Demo1/1-adams-plusByron-1-manifest.csv"); - MANIFESTS.add(dataPath + "Demo1/2-alamosa-plusByron-2-manifest.csv"); - MANIFESTS.add(dataPath + "split-Byron/Byron-3-manifest.csv"); - MANIFESTS.add(dataPath + "NewSouthWales21/Kempsey_Mayoral.manifest.csv"); - MANIFESTS.add(dataPath + "split-Byron/Byron-5-manifest.csv"); - MANIFESTS.add(dataPath + "split-Byron/Byron-6-manifest.csv"); - MANIFESTS.add(dataPath + "Boulder23/Boulder-IRV-Manifest.csv"); - - for(int i = 8; i < 65; ++i){ - CVRS.add(dataPath + "split-Byron/Byron-" + i + ".csv"); - MANIFESTS.add(dataPath + "split-Byron/Byron-" + i + "-manifest.csv"); - } - - for(int i = 1; i < 65; ++i){ + for(int i = 1; i < 2; ++i){ uploadCounty(i, "cvr-export", CVRS.get(i-1), CVRS.get(i-1) + ".sha256sum"); uploadCounty(i, "ballot-manifest", MANIFESTS.get(i-1), MANIFESTS.get(i-1) + ".sha256sum"); } - } + + // Now do the sample size estimate + List estimateData = getSampleSizeEstimates(); + } diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java index 22f5370c..456b62e1 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java @@ -24,6 +24,7 @@ import static io.restassured.RestAssured.given; import static org.testng.Assert.assertEquals; +import au.org.democracydevelopers.corla.endpoint.EstimateSampleSizes; import io.restassured.RestAssured; import io.restassured.filter.session.SessionFilter; import io.restassured.response.Response; @@ -32,6 +33,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; @@ -140,6 +143,10 @@ protected void uploadCounty(final int number, final String fileType, logout(filter, user); } + protected List getSampleSizeEstimates() { + return new ArrayList<>(); + } + /** * Read a string from a file. * Catches the IO exception and returns "" if file can't be opened. From bb2135e4a3232bffd4eb6785a76226b34ef1f5ea Mon Sep 17 00:00:00 2001 From: vteague Date: Mon, 4 Nov 2024 15:03:01 +1100 Subject: [PATCH 15/40] Some parsing of DoSDashboard responses. --- .../EstimateSampleSizesVaryingManifests.java | 11 +++--- .../corla/workflows/Workflow.java | 39 +++++++++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java index 2a896952..2e33de06 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java @@ -55,8 +55,8 @@ public class EstimateSampleSizesVaryingManifests extends Workflow { /** * This "test" uploads CVRs and ballot manifests. */ - @Test(enabled = false) - public void runManifestVaryingDemo(){ + @Test(enabled = true) + public void runManifestVaryingDemo() { List CVRS = new ArrayList<>(); CVRS.add(dataPath + "PluralityOnly/Plurality1And2.csv"); @@ -64,13 +64,14 @@ public void runManifestVaryingDemo(){ List MANIFESTS = new ArrayList<>(); MANIFESTS.add(dataPath + "PluralityOnly/ThreeCandidatesTenVotes_Manifest.csv"); - for(int i = 1; i < 2; ++i){ - uploadCounty(i, "cvr-export", CVRS.get(i-1), CVRS.get(i-1) + ".sha256sum"); - uploadCounty(i, "ballot-manifest", MANIFESTS.get(i-1), MANIFESTS.get(i-1) + ".sha256sum"); + for (int i = 1; i < 2; ++i) { + uploadCounty(i, "cvr-export", CVRS.get(i - 1), CVRS.get(i - 1) + ".sha256sum"); + uploadCounty(i, "ballot-manifest", MANIFESTS.get(i - 1), MANIFESTS.get(i - 1) + ".sha256sum"); } // Now do the sample size estimate List estimateData = getSampleSizeEstimates(); + } } diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java index 456b62e1..70897279 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java @@ -23,8 +23,10 @@ import static io.restassured.RestAssured.given; import static org.testng.Assert.assertEquals; +import static us.freeandfair.corla.Main.GSON; import au.org.democracydevelopers.corla.endpoint.EstimateSampleSizes; +import com.google.gson.Gson; import io.restassured.RestAssured; import io.restassured.filter.session.SessionFilter; import io.restassured.response.Response; @@ -36,9 +38,15 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.SortedMap; + import org.apache.log4j.LogManager; import org.apache.log4j.Logger; +import org.apache.http.HttpStatus; import org.testng.annotations.BeforeClass; +import us.freeandfair.corla.json.DoSDashboardRefreshResponse; +import us.freeandfair.corla.model.AuditReason; +import us.freeandfair.corla.model.DoSDashboard; import wiremock.net.minidev.json.JSONObject; /** @@ -144,6 +152,37 @@ protected void uploadCounty(final int number, final String fileType, } protected List getSampleSizeEstimates() { + final SessionFilter filter = new SessionFilter(); + + // Login as state admin. + authenticate(filter, "stateadmin1", "", 1); + authenticate(filter, "stateadmin1", "s d f", 2); + + // GET the state dashboard. This is just to test that the login worked. + // DoSDashboardRefreshResponse DoSDashboard + Map estimatedBallotsToAudit + = given() + .filter(filter) + .header("Content-Type", "application/json") + .get("/dos-dashboard") + .then() + .assertThat() + .statusCode(HttpStatus.SC_OK) + .extract() + .body() + .jsonPath() + .getMap("estimated_ballots_to_audit", Long.class, Integer.class); + + // TODO: This throws errors relating to parsing of enums. Not sure exactly why. + // DoSDashboardRefreshResponse DoSDasboard = GSON.fromJson(data, DoSDashboardRefreshResponse.class); + // Similarly, so does getting the response and then calling + // .as(DoSDashboardRefreshResponse.class); + + //Response response = given() + // .filter(filter) + // .header("Content-Type", "text/csv") + // .post("/estimate-sample-sizes"); + return new ArrayList<>(); } From fc6042cbd34c8e5c7e7acaf236afee6efd6fe3b9 Mon Sep 17 00:00:00 2001 From: vteague Date: Mon, 4 Nov 2024 15:52:59 +1100 Subject: [PATCH 16/40] Basic sanity checking of DoSDashboard response. --- .../EstimateSampleSizesVaryingManifests.java | 31 +++++ .../corla/workflows/Workflow.java | 110 ++++++++++-------- 2 files changed, 94 insertions(+), 47 deletions(-) diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java index 2e33de06..7060855e 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java @@ -22,12 +22,16 @@ package au.org.democracydevelopers.corla.workflows; import au.org.democracydevelopers.corla.endpoint.EstimateSampleSizes; +import io.restassured.path.json.JsonPath; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.testng.annotations.Test; +import static org.testng.Assert.*; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; /** * A demonstration workflow that tests sample size estimation with and without manifests, comparing @@ -69,6 +73,33 @@ public void runManifestVaryingDemo() { uploadCounty(i, "ballot-manifest", MANIFESTS.get(i - 1), MANIFESTS.get(i - 1) + ".sha256sum"); } + // Get the DoSDashboard refresh response; sanity check. + JsonPath response = getDoSDashBoardRefreshResponse(); + + // This should really be but I can't see how to tell the parser how to deal + // with the enum. Anyway, the string is fine for testing. + Map auditReasons = response + .getMap("audited_contests", Long.class, String.class); + + Map estimatedBallotsToAudit = response + .getMap("estimated_ballots_to_audit", Long.class, Integer.class); + + // For now, just check that there are some estimates. + assertFalse(estimatedBallotsToAudit.isEmpty()); + + Map optimisticBallotsToAudit = response + .getMap("optimistic_ballots_to_audit", Long.class, Integer.class); + + // For now, just check that there are some estimates. + assertFalse(optimisticBallotsToAudit.isEmpty()); + + // This is a linked hash map describing the various kinds of audit info, including targeted + // contest, risk limit, etc. + var auditInfo = response.get("audit_info"); + + // Again, this should be an ASMState enum, but because of enum parsing issues we just get the string. + String ASMState = response.getString("asm_state"); + // Now do the sample size estimate List estimateData = getSampleSizeEstimates(); diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java index 70897279..9df73375 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java @@ -22,29 +22,31 @@ package au.org.democracydevelopers.corla.workflows; import static io.restassured.RestAssured.given; +import static io.restassured.RestAssured.when; import static org.testng.Assert.assertEquals; import static us.freeandfair.corla.Main.GSON; import au.org.democracydevelopers.corla.endpoint.EstimateSampleSizes; import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; import io.restassured.RestAssured; import io.restassured.filter.session.SessionFilter; +import io.restassured.path.json.JsonPath; import io.restassured.response.Response; import java.io.*; +import java.lang.reflect.Type; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.SortedMap; +import java.util.*; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.apache.http.HttpStatus; import org.testng.annotations.BeforeClass; import us.freeandfair.corla.json.DoSDashboardRefreshResponse; +import us.freeandfair.corla.model.AuditInfo; import us.freeandfair.corla.model.AuditReason; import us.freeandfair.corla.model.DoSDashboard; import wiremock.net.minidev.json.JSONObject; @@ -60,13 +62,19 @@ public class Workflow { */ private static final Logger LOGGER = LogManager.getLogger(Workflow.class); + /** + * Used for deserializing responses. + */ + private static final Type AUDIT_REASON_SET = new TypeToken>() { + }.getType(); + @BeforeClass public void setup() { RestAssured.baseURI = "http://localhost"; RestAssured.port = 8888; } - protected JSONObject createBody(final Map data){ + protected JSONObject createBody(final Map data) { JSONObject body = new JSONObject(); body.putAll(data); return body; @@ -74,12 +82,13 @@ protected JSONObject createBody(final Map data){ /** * Authenticate the given user with the given password/second factor challenge answer. + * * @param filter Session filter to maintain same session across API test. - * @param user Username to authenticate - * @param pwd Password/second factor challenge answer for user. - * @param stage Authentication stage. + * @param user Username to authenticate + * @param pwd Password/second factor challenge answer for user. + * @param stage Authentication stage. */ - protected void authenticate(final SessionFilter filter, final String user, final String pwd, final int stage){ + protected void authenticate(final SessionFilter filter, final String user, final String pwd, final int stage) { final JSONObject requestParams = (stage == 1) ? createBody(Map.of("username", user, "password", pwd)) : createBody(Map.of("username", user, "second_factor", pwd)); @@ -91,17 +100,18 @@ protected void authenticate(final SessionFilter filter, final String user, final final String authStatus = response.getBody().jsonPath().getString("stage"); - LOGGER.debug("Auth status for login "+user+" stage "+stage+" is "+authStatus); + LOGGER.debug("Auth status for login " + user + " stage " + stage + " is " + authStatus); assertEquals(authStatus, (stage == 1) ? "TRADITIONALLY_AUTHENTICATED" : - "SECOND_FACTOR_AUTHENTICATED", "Stage "+stage+" auth failed."); + "SECOND_FACTOR_AUTHENTICATED", "Stage " + stage + " auth failed."); } /** * Unauthenticate the given user. + * * @param filter Session filter to maintain same session across API test. - * @param user Username to unauthenticate. + * @param user Username to unauthenticate. */ - protected void logout(final SessionFilter filter, final String user){ + protected void logout(final SessionFilter filter, final String user) { JSONObject requestParams = createBody(Map.of("username", user)); given() @@ -113,12 +123,13 @@ protected void logout(final SessionFilter filter, final String user){ /** * Upload a file and its corresponding hash on behalf of the given county number. - * @param number Number of the county uploading the CVR/hash. - * @param file Path of the file to be uploaded. + * + * @param number Number of the county uploading the CVR/hash. + * @param file Path of the file to be uploaded. * @param hashFile Path of the corresponding hash for the CVR file. */ protected void uploadCounty(final int number, final String fileType, - final String file, final String hashFile){ + final String file, final String hashFile) { final SessionFilter filter = new SessionFilter(); final String user = "countyadmin" + number; @@ -151,38 +162,43 @@ protected void uploadCounty(final int number, final String fileType, logout(filter, user); } - protected List getSampleSizeEstimates() { - final SessionFilter filter = new SessionFilter(); - - // Login as state admin. - authenticate(filter, "stateadmin1", "", 1); - authenticate(filter, "stateadmin1", "s d f", 2); - - // GET the state dashboard. This is just to test that the login worked. - // DoSDashboardRefreshResponse DoSDashboard - Map estimatedBallotsToAudit - = given() - .filter(filter) - .header("Content-Type", "application/json") - .get("/dos-dashboard") - .then() - .assertThat() - .statusCode(HttpStatus.SC_OK) - .extract() - .body() - .jsonPath() - .getMap("estimated_ballots_to_audit", Long.class, Integer.class); - - // TODO: This throws errors relating to parsing of enums. Not sure exactly why. - // DoSDashboardRefreshResponse DoSDasboard = GSON.fromJson(data, DoSDashboardRefreshResponse.class); - // Similarly, so does getting the response and then calling - // .as(DoSDashboardRefreshResponse.class); - - //Response response = given() - // .filter(filter) - // .header("Content-Type", "text/csv") - // .post("/estimate-sample-sizes"); + protected JsonPath getDoSDashBoardRefreshResponse() { + final SessionFilter filter = new SessionFilter(); + + // Login as state admin. + authenticate(filter, "stateadmin1","",1); + + authenticate(filter, "stateadmin1","s d f",2); + + // GET the state dashboard. This is just to test that the login worked. + // DoSDashboardRefreshResponse DoSDashboard + return + given() + .filter(filter) + .header("Content-Type", "application/json") + .get("/dos-dashboard") + .then() + .assertThat() + .statusCode(HttpStatus.SC_OK) + .extract() + .body() + .jsonPath(); + + + // TODO: This would be a lot simpler if it just returned a DoSDashBoardRefreshResponse via + // DoSDashboardRefreshResponse DoSDasboard = GSON.fromJson(data, DoSDashboardRefreshResponse.class); + // but that throws errors relating to parsing of enums. Not sure exactly why. + // Similarly, so does getting the response and then calling + // .as(DoSDashboardRefreshResponse.class); + // So I've left it as a JsonPath, from which you can collect the fields by name. + + //Response response = given() + // .filter(filter) + // .header("Content-Type", "text/csv") + // .post("/estimate-sample-sizes"); +} + protected List getSampleSizeEstimates() { return new ArrayList<>(); } From 13ebdda72e750ebe0c212c9b0173e62d465c1b27 Mon Sep 17 00:00:00 2001 From: vteague Date: Mon, 4 Nov 2024 18:31:22 +1100 Subject: [PATCH 17/40] Set and sanity-check risk limit. --- .../corla/util/TestClassWithDatabase.java | 2 +- .../corla/workflows/Demo1.java | 51 ++++++- .../corla/workflows/Workflow.java | 136 +++++++++++------- 3 files changed, 134 insertions(+), 55 deletions(-) diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/util/TestClassWithDatabase.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/util/TestClassWithDatabase.java index 37b9ecd7..c70c2448 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/util/TestClassWithDatabase.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/util/TestClassWithDatabase.java @@ -44,7 +44,7 @@ public abstract class TestClassWithDatabase { protected static final Properties blank = new Properties(); /** - * Oroperties derived from test.properties. + * Properties derived from test.properties. */ protected static Properties config = loadProperties(); diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java index 41d88160..b7e03f72 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java @@ -21,17 +21,27 @@ package au.org.democracydevelopers.corla.workflows; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; + +import io.restassured.path.json.JsonPath; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.ext.ScriptUtils; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +import us.freeandfair.corla.persistence.Persistence; + +import static org.testng.Assert.assertEquals; +import static us.freeandfair.corla.asm.ASMState.DoSDashboardState.DOS_INITIAL_STATE; /** * A demonstration workflow that uploads CVRs and ballot manifests for all 64 counties. * This assumes that main is running. */ -@Test(enabled=false) +@Test(enabled=true) public class Demo1 extends Workflow { /** @@ -44,14 +54,39 @@ public class Demo1 extends Workflow { */ private static final Logger LOGGER = LogManager.getLogger(Demo1.class); + /** + * Container for the mock-up database. + */ + private final static PostgreSQLContainer postgres = createTestContainer(); + + /** + * Database init. + */ + @BeforeClass + public static void beforeAll() { + + final var containerDelegate = setupContainerStartPostgres(postgres); + + var s = Persistence.openSession(); + s.beginTransaction(); + + // Used to initialize the database, e.g. to set the ASM state to the DOS_INITIAL_STATE + // and to insert counties and administrator test logins. + ScriptUtils.runInitScript(containerDelegate, "SQL/co-counties.sql"); + + runMain("Demo1"); + } + /** * This "test" uploads CVRs and ballot manifests for all 64 counties. * The uploads match the following http files in the workflows directory: * - demo1_loadCVRs, demo1_loadManifests, * - Boulder_loadCVRs, Boulder_loadManifest. */ - @Test(enabled = false) + @Test(enabled = true) public void runDemo1(){ + + List CVRS = new ArrayList<>(); CVRS.add(dataPath + "Demo1/1-adams-cvrexport-plusByron-1.csv"); @@ -82,6 +117,18 @@ public void runDemo1(){ uploadCounty(i, "cvr-export", CVRS.get(i-1), CVRS.get(i-1) + ".sha256sum"); uploadCounty(i, "ballot-manifest", MANIFESTS.get(i-1), MANIFESTS.get(i-1) + ".sha256sum"); } + + // Get the DoSDashboard refresh response; sanity check. + JsonPath response = getDoSDashBoardRefreshResponse(); + assertEquals(response.get("asm_state"), DOS_INITIAL_STATE.toString()); + + // Set the audit info, including the canonical list and the risk limit. + final BigDecimal riskLimit = BigDecimal.valueOf(0.03); + updateAuditInfo(dataPath + "Demo1/" + "demo1-canonical-list.csv", riskLimit); + + JsonPath response2 = getDoSDashBoardRefreshResponse(); + assertEquals(0, riskLimit + .compareTo(new BigDecimal(response2.getMap("audit_info").get("risk_limit").toString()))); } } diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java index 9df73375..cc66e57a 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java @@ -22,20 +22,18 @@ package au.org.democracydevelopers.corla.workflows; import static io.restassured.RestAssured.given; -import static io.restassured.RestAssured.when; import static org.testng.Assert.assertEquals; -import static us.freeandfair.corla.Main.GSON; +import static us.freeandfair.corla.Main.main; import au.org.democracydevelopers.corla.endpoint.EstimateSampleSizes; -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; +import au.org.democracydevelopers.corla.util.TestClassWithDatabase; import io.restassured.RestAssured; import io.restassured.filter.session.SessionFilter; import io.restassured.path.json.JsonPath; import io.restassured.response.Response; import java.io.*; -import java.lang.reflect.Type; +import java.math.BigDecimal; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -45,17 +43,13 @@ import org.apache.log4j.Logger; import org.apache.http.HttpStatus; import org.testng.annotations.BeforeClass; -import us.freeandfair.corla.json.DoSDashboardRefreshResponse; -import us.freeandfair.corla.model.AuditInfo; -import us.freeandfair.corla.model.AuditReason; -import us.freeandfair.corla.model.DoSDashboard; import wiremock.net.minidev.json.JSONObject; /** * Base class for an API test workflow designed to run through a sequence of steps involving * a sequence of endpoint accesses. */ -public class Workflow { +public class Workflow extends TestClassWithDatabase { /** * Class-wide logger @@ -63,10 +57,9 @@ public class Workflow { private static final Logger LOGGER = LogManager.getLogger(Workflow.class); /** - * Used for deserializing responses. + * Path for storing temporary config files */ - private static final Type AUDIT_REASON_SET = new TypeToken>() { - }.getType(); + private static final String tempConfigPath = "src/test/workflows/temp/"; @BeforeClass public void setup() { @@ -74,6 +67,25 @@ public void setup() { RestAssured.port = 8888; } + /** + * Run main, using the psql container as its database. Main can take (database) properties as a + * CLI, but only as a file, so we need to make the file and then tell main to read it. + * @param testFileName the name of the test file - must be different for each test. + */ + protected static void runMain(String testFileName) { + final String propertiesFile = tempConfigPath +testFileName+"-test.properties"; + try { + FileOutputStream os = new FileOutputStream(propertiesFile); + StringWriter sw = new StringWriter(); + config.store(sw, "Ephemeral database config for Demo1"); + os.write(sw.toString().getBytes()); + os.close(); + main(propertiesFile); + } catch (Exception e) { + LOGGER.error("Couldn't write Demo1-test.properties", e); + } + } + protected JSONObject createBody(final Map data) { JSONObject body = new JSONObject(); body.putAll(data); @@ -131,12 +143,8 @@ protected void logout(final SessionFilter filter, final String user) { protected void uploadCounty(final int number, final String fileType, final String file, final String hashFile) { - final SessionFilter filter = new SessionFilter(); final String user = "countyadmin" + number; - - // Login as the county. - authenticate(filter, user, "", 1); - authenticate(filter, user, "s d f", 2); + SessionFilter filter = doLogin(user); // GET the county dashboard. This is just to test that the login worked. given().filter(filter).get("/county-dashboard"); @@ -162,46 +170,63 @@ protected void uploadCounty(final int number, final String fileType, logout(filter, user); } + /** + * Get the DoSDashboardRefreshResponse, as a JSONPath object, which contains basically everything + * about the current status of the audit. + * Also tests that the http response is OK. + * @return the DosDashboardRefreshResponse. + */ protected JsonPath getDoSDashBoardRefreshResponse() { - final SessionFilter filter = new SessionFilter(); - - // Login as state admin. - authenticate(filter, "stateadmin1","",1); - - authenticate(filter, "stateadmin1","s d f",2); - - // GET the state dashboard. This is just to test that the login worked. - // DoSDashboardRefreshResponse DoSDashboard - return - given() - .filter(filter) - .header("Content-Type", "application/json") - .get("/dos-dashboard") - .then() - .assertThat() - .statusCode(HttpStatus.SC_OK) - .extract() - .body() - .jsonPath(); - - - // TODO: This would be a lot simpler if it just returned a DoSDashBoardRefreshResponse via - // DoSDashboardRefreshResponse DoSDasboard = GSON.fromJson(data, DoSDashboardRefreshResponse.class); - // but that throws errors relating to parsing of enums. Not sure exactly why. - // Similarly, so does getting the response and then calling - // .as(DoSDashboardRefreshResponse.class); - // So I've left it as a JsonPath, from which you can collect the fields by name. - - //Response response = given() - // .filter(filter) - // .header("Content-Type", "text/csv") - // .post("/estimate-sample-sizes"); -} + // TODO: This would be a lot simpler if it just returned a DoSDashBoardRefreshResponse via + // DoSDashboardRefreshResponse DoSDasboard = GSON.fromJson(data, DoSDashboardRefreshResponse.class); + // but that throws errors relating to parsing of enums. Not sure exactly why. + // Similarly, so does getting the response and then calling + // .as(DoSDashboardRefreshResponse.class); + // So I've left it as a JsonPath, from which you can collect the fields by name. + + // Login as state admin. + final SessionFilter filter = doLogin("stateadmin1"); + + return given() + .filter(filter) + .header("Content-Type", "application/json") + .get("/dos-dashboard") + .then() + .assertThat() + .statusCode(HttpStatus.SC_OK) + .extract() + .body() + .jsonPath(); + } protected List getSampleSizeEstimates() { return new ArrayList<>(); } + /** + * Used by DoS admin to set audit info, including risk limit and canonical list. + * Sets the election date to today and the public meeting date to one week from today. + * @param canonicalListFile the path to the canonical list csv file. + * @param riskLimit the risk limit. + */ + protected void updateAuditInfo(String canonicalListFile, BigDecimal riskLimit) { + + // Login as state admin. + final SessionFilter filter = doLogin("stateadmin1"); + + JSONObject requestParams = new JSONObject(); + requestParams.put("risk_limit", 0.03); + + given() + .filter(filter) + .header("Content-Type", "application/json") + .body(requestParams.toString()) // toJSONString? + .post("/update-audit-info") + .then() + .assertThat() + .statusCode(HttpStatus.SC_OK); + } + /** * Read a string from a file. * Catches the IO exception and returns "" if file can't be opened. @@ -219,4 +244,11 @@ private String readFromFile(final String fileName) { } } + private SessionFilter doLogin(String username) { + final SessionFilter filter = new SessionFilter(); + authenticate(filter, username,"",1); + authenticate(filter, username,"s d f",2); + return filter; + } + } From 59cce9d1255e1e884220cb681040aab62202bd87 Mon Sep 17 00:00:00 2001 From: vteague Date: Wed, 6 Nov 2024 15:26:27 +1100 Subject: [PATCH 18/40] Complete audit setup in Demo1. --- .../corla/workflows/Demo1.java | 44 ++++++++++++++----- .../EstimateSampleSizesVaryingManifests.java | 2 + .../corla/workflows/Workflow.java | 39 ++++++++++++++-- 3 files changed, 70 insertions(+), 15 deletions(-) diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java index b7e03f72..49334751 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java @@ -34,8 +34,8 @@ import org.testng.annotations.Test; import us.freeandfair.corla.persistence.Persistence; -import static org.testng.Assert.assertEquals; -import static us.freeandfair.corla.asm.ASMState.DoSDashboardState.DOS_INITIAL_STATE; +import static org.testng.Assert.*; +import static us.freeandfair.corla.asm.ASMState.DoSDashboardState.*; /** * A demonstration workflow that uploads CVRs and ballot manifests for all 64 counties. @@ -108,27 +108,49 @@ public void runDemo1(){ MANIFESTS.add(dataPath + "split-Byron/Byron-6-manifest.csv"); MANIFESTS.add(dataPath + "Boulder23/Boulder-IRV-Manifest.csv"); - for(int i = 8; i < 65; ++i){ + for(int i = 8 ; i <= numCounties ; ++i){ CVRS.add(dataPath + "split-Byron/Byron-" + i + ".csv"); MANIFESTS.add(dataPath + "split-Byron/Byron-" + i + "-manifest.csv"); } - for(int i = 1; i < 65; ++i){ - uploadCounty(i, "cvr-export", CVRS.get(i-1), CVRS.get(i-1) + ".sha256sum"); + // First upload all the manifests + for(int i = 1 ; i <= numCounties ; ++i){ uploadCounty(i, "ballot-manifest", MANIFESTS.get(i-1), MANIFESTS.get(i-1) + ".sha256sum"); } - // Get the DoSDashboard refresh response; sanity check. - JsonPath response = getDoSDashBoardRefreshResponse(); + // Then upload all the CVRs. The order is important because it's an error to try to import a manifest while the CVRs + // are being read. + for(int i = 1; i <= numCounties ; ++i){ + uploadCounty(i, "cvr-export", CVRS.get(i-1), CVRS.get(i-1) + ".sha256sum"); + } + + // Get the DoSDashboard refresh response; sanity check for initial state. + final JsonPath response = getDoSDashBoardRefreshResponse(); assertEquals(response.get("asm_state"), DOS_INITIAL_STATE.toString()); + assertEquals(response.getMap("audit_info.canonicalChoices").toString(), "{}"); + assertNull(response.get("audit_info.risk_limit")); + assertNull(response.get("audit_info.seed")); - // Set the audit info, including the canonical list and the risk limit. + // Set the audit info, including the canonical list and the risk limit; check for update. final BigDecimal riskLimit = BigDecimal.valueOf(0.03); updateAuditInfo(dataPath + "Demo1/" + "demo1-canonical-list.csv", riskLimit); - - JsonPath response2 = getDoSDashBoardRefreshResponse(); + final JsonPath response2 = getDoSDashBoardRefreshResponse(); assertEquals(0, riskLimit - .compareTo(new BigDecimal(response2.getMap("audit_info").get("risk_limit").toString()))); + .compareTo(new BigDecimal(response2.get("audit_info.risk_limit").toString()))); + // There should be canonical contests for each county. + assertEquals(numCounties, response2.getMap("audit_info.canonicalContests").values().size()); + // Check that the seed is still null. + assertNull(response2.get("audit_info.seed")); + assertEquals(response2.get("asm_state"), PARTIAL_AUDIT_INFO_SET.toString()); + + // Set the seed. + final String seed = "9823749812374981273489712389471238974"; + setSeed(seed); + // This should be complete audit info. + final JsonPath response3 = getDoSDashBoardRefreshResponse(); + assertEquals(response3.get("audit_info.seed"), seed); + assertEquals(response3.get("asm_state"), COMPLETE_AUDIT_INFO_SET.toString()); + } } diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java index 7060855e..2181d0f8 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java @@ -96,6 +96,8 @@ public void runManifestVaryingDemo() { // This is a linked hash map describing the various kinds of audit info, including targeted // contest, risk limit, etc. var auditInfo = response.get("audit_info"); + var seed = response.get("audit_info.seed"); + var RiskLimit = response.getDouble("audit_info.risk_limit"); // Again, this should be an ASMState enum, but because of enum parsing issues we just get the string. String ASMState = response.getString("asm_state"); diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java index cc66e57a..4fcc521c 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java @@ -51,6 +51,11 @@ */ public class Workflow extends TestClassWithDatabase { + /** + * Number of CO counties + */ + protected static final int numCounties = 64; + /** * Class-wide logger */ @@ -205,7 +210,7 @@ protected List getSampleSizeEstimates() { /** * Used by DoS admin to set audit info, including risk limit and canonical list. - * Sets the election date to today and the public meeting date to one week from today. + * Sets the election date to an arbitrary date and the public meeting for one week later. * @param canonicalListFile the path to the canonical list csv file. * @param riskLimit the risk limit. */ @@ -215,18 +220,44 @@ protected void updateAuditInfo(String canonicalListFile, BigDecimal riskLimit) { final SessionFilter filter = doLogin("stateadmin1"); JSONObject requestParams = new JSONObject(); - requestParams.put("risk_limit", 0.03); + requestParams.put("risk_limit", riskLimit); + requestParams.put("election_date","2024-09-15T05:42:17.796Z"); + requestParams.put("election_type","general"); + requestParams.put("public_meeting_date","2024-09-22T05:42:22.037Z"); + JSONObject canonicalListContents = new JSONObject(); + canonicalListContents.put("contents",readFromFile(canonicalListFile)); + requestParams.put("upload_file", List.of(canonicalListContents)); given() .filter(filter) .header("Content-Type", "application/json") - .body(requestParams.toString()) // toJSONString? + .body(requestParams.toString()) .post("/update-audit-info") .then() .assertThat() .statusCode(HttpStatus.SC_OK); } + /** + * Set the seed for the audit. + */ + protected void setSeed(String seed) { + // Login as state admin. + final SessionFilter filter = doLogin("stateadmin1"); + + JSONObject requestParams = new JSONObject(); + requestParams.put("seed", seed); + + given() + .filter(filter) + .header("Content-Type", "application/json") + .body(requestParams.toString()) + .post("/random-seed") + .then() + .assertThat() + .statusCode(HttpStatus.SC_OK); + } + /** * Read a string from a file. * Catches the IO exception and returns "" if file can't be opened. @@ -239,7 +270,7 @@ private String readFromFile(final String fileName) { Path path = Paths.get(fileName); return String.join("\n",Files.readAllLines(path)); } catch (IOException ex) { - LOGGER.error(prefix + ex.getMessage()); + LOGGER.error(prefix + ex.getMessage()); return ""; } } From 63f4948c11b36f686d8bdc62513533cc1de59826 Mon Sep 17 00:00:00 2001 From: vteague Date: Thu, 7 Nov 2024 17:48:09 +1100 Subject: [PATCH 19/40] Contest targeting working for plurality contests. --- .../corla/workflows/Demo1.java | 37 +++++---- .../corla/workflows/Workflow.java | 75 ++++++++++++++++++- 2 files changed, 98 insertions(+), 14 deletions(-) diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java index 49334751..e94568ca 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java @@ -24,6 +24,7 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; +import java.util.Map; import io.restassured.path.json.JsonPath; import org.apache.log4j.LogManager; @@ -125,31 +126,41 @@ public void runDemo1(){ } // Get the DoSDashboard refresh response; sanity check for initial state. - final JsonPath response = getDoSDashBoardRefreshResponse(); - assertEquals(response.get("asm_state"), DOS_INITIAL_STATE.toString()); - assertEquals(response.getMap("audit_info.canonicalChoices").toString(), "{}"); - assertNull(response.get("audit_info.risk_limit")); - assertNull(response.get("audit_info.seed")); + JsonPath dashboard = getDoSDashBoardRefreshResponse(); + assertEquals(dashboard.get("asm_state"), DOS_INITIAL_STATE.toString()); + assertEquals(dashboard.getMap("audit_info.canonicalChoices").toString(), "{}"); + assertNull(dashboard.get("audit_info.risk_limit")); + assertNull(dashboard.get("audit_info.seed")); // Set the audit info, including the canonical list and the risk limit; check for update. final BigDecimal riskLimit = BigDecimal.valueOf(0.03); updateAuditInfo(dataPath + "Demo1/" + "demo1-canonical-list.csv", riskLimit); - final JsonPath response2 = getDoSDashBoardRefreshResponse(); + dashboard = getDoSDashBoardRefreshResponse(); assertEquals(0, riskLimit - .compareTo(new BigDecimal(response2.get("audit_info.risk_limit").toString()))); + .compareTo(new BigDecimal(dashboard.get("audit_info.risk_limit").toString()))); // There should be canonical contests for each county. - assertEquals(numCounties, response2.getMap("audit_info.canonicalContests").values().size()); + assertEquals(numCounties, dashboard.getMap("audit_info.canonicalContests").values().size()); // Check that the seed is still null. - assertNull(response2.get("audit_info.seed")); - assertEquals(response2.get("asm_state"), PARTIAL_AUDIT_INFO_SET.toString()); + assertNull(dashboard.get("audit_info.seed")); + assertEquals(dashboard.get("asm_state"), PARTIAL_AUDIT_INFO_SET.toString()); + + // Generate assertions; sanity check + // TODO this is commented out for now while we figure out how to run the raire-service in the Docker container. + // generateAssertions(1); + // dashboard = getDoSDashBoardRefreshResponse(); + // There should be 4 IRV contests. + // assertEquals(4, dashboard.getList("generate_assertions_summaries").size()); + + // For now, just target plurality contests. + targetContests(Map.of("City of Longmont - Mayor","COUNTY_WIDE_CONTEST")); // Set the seed. final String seed = "9823749812374981273489712389471238974"; setSeed(seed); // This should be complete audit info. - final JsonPath response3 = getDoSDashBoardRefreshResponse(); - assertEquals(response3.get("audit_info.seed"), seed); - assertEquals(response3.get("asm_state"), COMPLETE_AUDIT_INFO_SET.toString()); + dashboard = getDoSDashBoardRefreshResponse(); + assertEquals(dashboard.get("audit_info.seed"), seed); + assertEquals(dashboard.get("asm_state"), COMPLETE_AUDIT_INFO_SET.toString()); } diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java index 4fcc521c..843d21c7 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java @@ -43,6 +43,8 @@ import org.apache.log4j.Logger; import org.apache.http.HttpStatus; import org.testng.annotations.BeforeClass; +import us.freeandfair.corla.model.Contest; +import wiremock.net.minidev.json.JSONArray; import wiremock.net.minidev.json.JSONObject; /** @@ -182,7 +184,7 @@ protected void uploadCounty(final int number, final String fileType, * @return the DosDashboardRefreshResponse. */ protected JsonPath getDoSDashBoardRefreshResponse() { - // TODO: This would be a lot simpler if it just returned a DoSDashBoardRefreshResponse via + // Note: this would be a lot simpler if it just returned a DoSDashBoardRefreshResponse via // DoSDashboardRefreshResponse DoSDasboard = GSON.fromJson(data, DoSDashboardRefreshResponse.class); // but that throws errors relating to parsing of enums. Not sure exactly why. // Similarly, so does getting the response and then calling @@ -238,6 +240,77 @@ protected void updateAuditInfo(String canonicalListFile, BigDecimal riskLimit) { .statusCode(HttpStatus.SC_OK); } + /** + * Generate assertions (for IRV contests) + * TODO At the moment this expects the raire-service to be running. + * Set it up so that we run raire-service inside the Docker container and tell main where to find it. + */ + protected void generateAssertions(double timeLimitSeconds) { + // Login as state admin. + final SessionFilter filter = doLogin("stateadmin1"); + + given() + .filter(filter) + .header("Content-Type", "application/x-www-form-urlencoded") + .get("/generate-assertions?timeLimitSeconds="+timeLimitSeconds) + .then() + .assertThat() + .statusCode(HttpStatus.SC_OK); + } + + /** + * Select contests to target, by name. + */ + protected void targetContests(Map targetedContestsWithReasons) { + // Login as state admin. + final SessionFilter filter = doLogin("stateadmin1"); + + // First get the contests. + // Again, this would be a lot easier if we could use .as(Contest[].class), but serialization is a problem. + final JsonPath contests = given() + .filter(filter) + .header("Content-Type", "text/plain") + .get("/contest") + .then() + .assertThat() + .statusCode(HttpStatus.SC_OK) + .extract() + .body() + .jsonPath(); + + // The contests and reasons to be requested. + JSONArray contestSelections = new JSONArray(); + + // Find the IDs of the ones we want to target. + for(int i=0 ; i < contests.getList("").size() ; i++) { + + final String contestName = contests.getString("[" + i + "].name"); + // If this contest's name is one of the targeted ones... + String reason = targetedContestsWithReasons.get(contestName); + if(reason != null) { + // add it to the selections. + final JSONObject contestSelection = new JSONObject(); + + final Integer contestId = contests.getInt("[" + i + "].id"); + contestSelection.put("audit","COMPARISON"); + contestSelection.put("contest",contestId); + contestSelection.put("reason", reason); + contestSelections.add(contestSelection); + } + } + + // Post the select-contests request + given() + .filter(filter) + .header("Content-Type", "application/json") + .body(contestSelections.toString()) + .post("/select-contests") + .then() + .assertThat() + .statusCode(HttpStatus.SC_OK); + + } + /** * Set the seed for the audit. */ From b193a0be881ac3190c4f0d6203629d771954d14a Mon Sep 17 00:00:00 2001 From: vteague Date: Thu, 7 Nov 2024 18:28:50 +1100 Subject: [PATCH 20/40] Estimate sample sizes added to workflow. --- .../corla/workflows/Demo1.java | 5 ++- .../corla/workflows/Workflow.java | 38 ++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java index e94568ca..5d7c3e12 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; +import au.org.democracydevelopers.corla.endpoint.EstimateSampleSizes; import io.restassured.path.json.JsonPath; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; @@ -95,7 +96,6 @@ public void runDemo1(){ CVRS.add(dataPath + "Demo1/3-arapahoe-Byron-3-plus-tied-irv.csv"); CVRS.add(dataPath + "Demo1/4-archuleta-kempsey-plusByron-4.csv"); CVRS.add(dataPath + "split-Byron/Byron-5.csv"); - CVRS.add(dataPath + "split-Byron/Byron-5.csv"); CVRS.add(dataPath + "split-Byron/Byron-6.csv"); CVRS.add(dataPath + "Demo1/7-boulder-2023-plusByron-7.csv"); @@ -162,6 +162,9 @@ public void runDemo1(){ assertEquals(dashboard.get("audit_info.seed"), seed); assertEquals(dashboard.get("asm_state"), COMPLETE_AUDIT_INFO_SET.toString()); + // Estimate sample sizes; sanity check. + List test = getSampleSizeEstimates(); + assertFalse(test.isEmpty()); } } diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java index 843d21c7..4ac28bc6 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java @@ -207,7 +207,43 @@ protected JsonPath getDoSDashBoardRefreshResponse() { } protected List getSampleSizeEstimates() { - return new ArrayList<>(); + + // Login as state admin. + final SessionFilter filter = doLogin("stateadmin1"); + + String data = given() + .filter(filter) + .get("/estimate-sample-sizes") + .then() + .assertThat() + .statusCode(HttpStatus.SC_OK) + .extract() + .body() + .asString(); + + List estimates = new ArrayList<>(); + var lines = data.split("\n"); + // Skip the first line (which has headrs) + for(int i = 1 ; i < lines.length ; i++) { + + var line = lines[i].split(","); + if(line.length < 7) { + throw new RuntimeException("Invalid sample size estimate data"); + } + + EstimateSampleSizes.EstimateData estimate = new EstimateSampleSizes.EstimateData( + line[0], + line[1], + line[2], + Integer.parseInt(line[3]), + Long.parseLong(line[4]), + new BigDecimal(line[5]), + Integer.parseInt(line[6]) + ); + estimates.add(estimate); + } + + return estimates; } /** From 627d512c0746161e71b97034abf856704e94cd49 Mon Sep 17 00:00:00 2001 From: vteague Date: Thu, 7 Nov 2024 20:28:04 +1100 Subject: [PATCH 21/40] Code cleanup. --- .../democracydevelopers/corla/workflows/Demo1.java | 6 ++++-- .../corla/workflows/Workflow.java | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java index 5d7c3e12..88029a24 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java @@ -163,8 +163,10 @@ public void runDemo1(){ assertEquals(dashboard.get("asm_state"), COMPLETE_AUDIT_INFO_SET.toString()); // Estimate sample sizes; sanity check. - List test = getSampleSizeEstimates(); - assertFalse(test.isEmpty()); + List sampleSizes = getSampleSizeEstimates(); + assertFalse(sampleSizes.isEmpty()); + + // TODO get assertions, sanity check. } } diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java index 4ac28bc6..14e2aed1 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java @@ -149,6 +149,7 @@ protected void logout(final SessionFilter filter, final String user) { */ protected void uploadCounty(final int number, final String fileType, final String file, final String hashFile) { + final String prefix = "[uploadCounty]"; final String user = "countyadmin" + number; SessionFilter filter = doLogin(user); @@ -173,6 +174,8 @@ protected void uploadCounty(final int number, final String fileType, .body(response.then().extract().asString()) .post("/import-" + fileType); + LOGGER.debug(String.format("%s %s %s %s.", prefix, "Successful file upload - ", user, fileType)); + // Logout. logout(filter, user); } @@ -206,7 +209,12 @@ protected JsonPath getDoSDashBoardRefreshResponse() { .jsonPath(); } + /** + * Get the sample size estimates CSV and return the parsed data. + * @return The sample size estimate data as a list of EstimateData structures. + */ protected List getSampleSizeEstimates() { + final String prefix = "[getSampleSizeEstimates]"; // Login as state admin. final SessionFilter filter = doLogin("stateadmin1"); @@ -223,12 +231,14 @@ protected List getSampleSizeEstimates() { List estimates = new ArrayList<>(); var lines = data.split("\n"); - // Skip the first line (which has headrs) + // Skip the first line (which has headers) for(int i = 1 ; i < lines.length ; i++) { var line = lines[i].split(","); if(line.length < 7) { - throw new RuntimeException("Invalid sample size estimate data"); + final String msg = prefix + " Invalid sample size estimate data"; + LOGGER.error(msg); + throw new RuntimeException(msg); } EstimateSampleSizes.EstimateData estimate = new EstimateSampleSizes.EstimateData( From de71322fb85933f5994a8e1060c5744467be2dc7 Mon Sep 17 00:00:00 2001 From: vteague Date: Fri, 8 Nov 2024 09:31:33 +1100 Subject: [PATCH 22/40] Enable Demo1; disable EstimateSampleSizesVaryingManifests. --- .../corla/workflows/Demo1.java | 15 ++- .../EstimateSampleSizesVaryingManifests.java | 115 +++++++++++------- 2 files changed, 83 insertions(+), 47 deletions(-) diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java index 88029a24..f7991324 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java @@ -114,13 +114,14 @@ public void runDemo1(){ MANIFESTS.add(dataPath + "split-Byron/Byron-" + i + "-manifest.csv"); } - // First upload all the manifests + // 1. First upload all the manifests for(int i = 1 ; i <= numCounties ; ++i){ uploadCounty(i, "ballot-manifest", MANIFESTS.get(i-1), MANIFESTS.get(i-1) + ".sha256sum"); } - // Then upload all the CVRs. The order is important because it's an error to try to import a manifest while the CVRs + // 2. Then upload all the CVRs. The order is important because it's an error to try to import a manifest while the CVRs // are being read. + // TODO Note this takes a while - we'll need to add a wait for anything that assumes all the CVRs are loaded. for(int i = 1; i <= numCounties ; ++i){ uploadCounty(i, "cvr-export", CVRS.get(i-1), CVRS.get(i-1) + ".sha256sum"); } @@ -132,7 +133,7 @@ public void runDemo1(){ assertNull(dashboard.get("audit_info.risk_limit")); assertNull(dashboard.get("audit_info.seed")); - // Set the audit info, including the canonical list and the risk limit; check for update. + // 3. Set the audit info, including the canonical list and the risk limit; check for update. final BigDecimal riskLimit = BigDecimal.valueOf(0.03); updateAuditInfo(dataPath + "Demo1/" + "demo1-canonical-list.csv", riskLimit); dashboard = getDoSDashBoardRefreshResponse(); @@ -144,25 +145,27 @@ public void runDemo1(){ assertNull(dashboard.get("audit_info.seed")); assertEquals(dashboard.get("asm_state"), PARTIAL_AUDIT_INFO_SET.toString()); - // Generate assertions; sanity check + // 4. Generate assertions; sanity check // TODO this is commented out for now while we figure out how to run the raire-service in the Docker container. // generateAssertions(1); // dashboard = getDoSDashBoardRefreshResponse(); // There should be 4 IRV contests. // assertEquals(4, dashboard.getList("generate_assertions_summaries").size()); + // 5. Choose targeted contests for audit. // For now, just target plurality contests. targetContests(Map.of("City of Longmont - Mayor","COUNTY_WIDE_CONTEST")); - // Set the seed. + // 6. Set the seed. final String seed = "9823749812374981273489712389471238974"; setSeed(seed); + // This should be complete audit info. dashboard = getDoSDashBoardRefreshResponse(); assertEquals(dashboard.get("audit_info.seed"), seed); assertEquals(dashboard.get("asm_state"), COMPLETE_AUDIT_INFO_SET.toString()); - // Estimate sample sizes; sanity check. + // 7. Estimate sample sizes; sanity check. List sampleSizes = getSampleSizeEstimates(); assertFalse(sampleSizes.isEmpty()); diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java index 2181d0f8..9d71a556 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java @@ -25,9 +25,16 @@ import io.restassured.path.json.JsonPath; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.ext.ScriptUtils; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +import us.freeandfair.corla.persistence.Persistence; + import static org.testng.Assert.*; +import static us.freeandfair.corla.asm.ASMState.DoSDashboardState.DOS_INITIAL_STATE; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -39,17 +46,19 @@ * throughout both sample size estimation and subsequent auditing. For example, when the manifest * exactly matches the CVR count, the samples should be the same; extras in the manifest should * increase the sample size, and a smaller manifest than CVR list should throw an error. - * All the tests here a the same plain plurality contest - we just vary the absence of presence of + * All the tests here are the same plain plurality contest - we just vary the absence of presence of * the manifest, and (if present) the number of ballots it states. - * This assumes that main is running. + * + * Note that it does not test for correct sample sizes, only for correct _changes_ to the sample + * sizes as a consequence of changes to the manifest. */ -@Test(enabled=true) +@Test(enabled=false) public class EstimateSampleSizesVaryingManifests extends Workflow { /** * Path for all the data files. */ - private static final String dataPath = "src/test/resources/CSVs/"; + private static final String dataPath = "src/test/resources/CSVs/PluralityOnly/"; /** * Class-wide logger @@ -57,54 +66,78 @@ public class EstimateSampleSizesVaryingManifests extends Workflow { private static final Logger LOGGER = LogManager.getLogger(EstimateSampleSizesVaryingManifests.class); /** - * This "test" uploads CVRs and ballot manifests. + * Container for the mock-up database. */ - @Test(enabled = true) - public void runManifestVaryingDemo() { - - List CVRS = new ArrayList<>(); - CVRS.add(dataPath + "PluralityOnly/Plurality1And2.csv"); + private final static PostgreSQLContainer postgres = createTestContainer(); - List MANIFESTS = new ArrayList<>(); - MANIFESTS.add(dataPath + "PluralityOnly/ThreeCandidatesTenVotes_Manifest.csv"); - - for (int i = 1; i < 2; ++i) { - uploadCounty(i, "cvr-export", CVRS.get(i - 1), CVRS.get(i - 1) + ".sha256sum"); - uploadCounty(i, "ballot-manifest", MANIFESTS.get(i - 1), MANIFESTS.get(i - 1) + ".sha256sum"); - } - - // Get the DoSDashboard refresh response; sanity check. - JsonPath response = getDoSDashBoardRefreshResponse(); + /** + * Database init. + */ + @BeforeClass + public static void beforeAll() { - // This should really be but I can't see how to tell the parser how to deal - // with the enum. Anyway, the string is fine for testing. - Map auditReasons = response - .getMap("audited_contests", Long.class, String.class); + final var containerDelegate = setupContainerStartPostgres(postgres); - Map estimatedBallotsToAudit = response - .getMap("estimated_ballots_to_audit", Long.class, Integer.class); + var s = Persistence.openSession(); + s.beginTransaction(); - // For now, just check that there are some estimates. - assertFalse(estimatedBallotsToAudit.isEmpty()); + // Used to initialize the database, e.g. to set the ASM state to the DOS_INITIAL_STATE + // and to insert counties and administrator test logins. + ScriptUtils.runInitScript(containerDelegate, "SQL/co-counties.sql"); - Map optimisticBallotsToAudit = response - .getMap("optimistic_ballots_to_audit", Long.class, Integer.class); + runMain("EstimateSampleSizesVaryingManifests"); + } - // For now, just check that there are some estimates. - assertFalse(optimisticBallotsToAudit.isEmpty()); + /** + * This "test" uploads CVRs and ballot manifests. + */ + @Test(enabled = false) + public void runManifestVaryingDemo() { - // This is a linked hash map describing the various kinds of audit info, including targeted - // contest, risk limit, etc. - var auditInfo = response.get("audit_info"); - var seed = response.get("audit_info.seed"); - var RiskLimit = response.getDouble("audit_info.risk_limit"); + List CVRS = new ArrayList<>(); + CVRS.add(dataPath + "Plurality1And2.csv"); - // Again, this should be an ASMState enum, but because of enum parsing issues we just get the string. - String ASMState = response.getString("asm_state"); - // Now do the sample size estimate - List estimateData = getSampleSizeEstimates(); + // Upload the CSVs but not the manifests. + uploadCounty(1, "cvr-export", CVRS.get(0), CVRS.get(0) + ".sha256sum"); + // Get the DoSDashboard refresh response; sanity check. + JsonPath dashboard = getDoSDashBoardRefreshResponse(); + assertEquals(dashboard.get("asm_state"), DOS_INITIAL_STATE.toString()); + + // Set the audit info, including the canonical list and the (stupidly large) risk limit; sanity check. + final BigDecimal riskLimit = BigDecimal.valueOf(0.5); + updateAuditInfo(dataPath + "Tiny_IRV_Boulder2023_Test_Canonical_List.csv", riskLimit); + dashboard = getDoSDashBoardRefreshResponse(); + assertEquals(0, riskLimit + .compareTo(new BigDecimal(dashboard.get("audit_info.risk_limit").toString()))); + + // 1. Estimate Sample sizes, without the manifest. This should count the CSVs. + List estimatesWithoutManifests = getSampleSizeEstimates(); + assertEquals(estimatesWithoutManifests.size(), 2); + + // 2. Upload the manifest that matches the CSV count, then get estimates. + // All the estimate data should be the same. + String matchingManifest = dataPath + "ThreeCandidatesTenVotes_Manifest.csv"; + uploadCounty(1, "ballot-manifest", matchingManifest, matchingManifest + ".sha256sum"); + List estimatesWithMatchingManifests = getSampleSizeEstimates(); + assertEquals(estimatesWithMatchingManifests.get(0), estimatesWithoutManifests.get(0)); + assertEquals(estimatesWithMatchingManifests.get(1), estimatesWithoutManifests.get(1)); + + // 3. Upload the manifest that claims double the CSV count. This should double the sample size + // estimate for Plurality2, but not for Plurality1 because it is already topped out at 10. + String doubledManifest = dataPath + "ThreeCandidatesTenVotes_DoubledManifest.csv"; + uploadCounty(1, "ballot-manifest", doubledManifest, doubledManifest + ".sha256sum"); + List estimatesWithDoubledManifests = getSampleSizeEstimates(); + assertEquals(estimatesWithDoubledManifests.size(), 2); + int plurality2index = 1; + if(estimatesWithDoubledManifests.get(0).contestName().equals("PluralityExample2")) { + plurality2index = 0; + } + assertEquals(estimatesWithoutManifests.get(1-plurality2index), + estimatesWithDoubledManifests.get(1-plurality2index)); + assertEquals(estimatesWithoutManifests.get(plurality2index).estimatedSamples(), + estimatesWithDoubledManifests.get(plurality2index).estimatedSamples()/2); } } From aeb89ca276d6672687992fc873e67df617d4869a Mon Sep 17 00:00:00 2001 From: vteague Date: Fri, 8 Nov 2024 09:40:57 +1100 Subject: [PATCH 23/40] Disable Demo1. --- .../au/org/democracydevelopers/corla/workflows/Demo1.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java index f7991324..37798de4 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java @@ -43,7 +43,7 @@ * A demonstration workflow that uploads CVRs and ballot manifests for all 64 counties. * This assumes that main is running. */ -@Test(enabled=true) +@Test(enabled=false) public class Demo1 extends Workflow { /** @@ -85,7 +85,7 @@ public static void beforeAll() { * - demo1_loadCVRs, demo1_loadManifests, * - Boulder_loadCVRs, Boulder_loadManifest. */ - @Test(enabled = true) + @Test(enabled = false) public void runDemo1(){ From 8abc0bbbaf9a717ec4b3d9ecc8e166bf8cc4467a Mon Sep 17 00:00:00 2001 From: vteague Date: Fri, 8 Nov 2024 10:16:13 +1100 Subject: [PATCH 24/40] Sample size testing functionality working. --- .../corla/workflows/Demo1.java | 4 +- .../EstimateSampleSizesVaryingManifests.java | 14 +-- ...t.csv => HundredVotes_DoubledManifest.csv} | 2 +- ...HundredVotes_DoubledManifest.csv.sha256sum | 1 + ... => HundredVotes_InsufficientManifest.csv} | 2 +- ...Manifest.csv => HundredVotes_Manifest.csv} | 2 +- .../HundredVotes_Manifest.csv.sha256sum | 1 + ...v => HundredVotes_OneAndAHalfManifest.csv} | 2 +- ...redVotes_OneAndAHalfManifest.csv.sha256sum | 1 + .../Plurality100votes2And10Margins.csv | 104 ++++++++++++++++++ ...urality100votes2And10Margins.csv.sha256sum | 1 + .../CSVs/PluralityOnly/Plurality1And2.csv | 14 --- .../Plurality1And2.csv.sha256sum | 1 - .../CSVs/PluralityOnly/PluralityTiedAnd2.csv | 14 --- .../PluralityTiedAnd2.csv.sha256sum | 1 - .../Plurality_Only_Test_Canonical_List.csv | 3 + ...eCandidatesTenVotes_Manifest.csv.sha256sum | 1 - ...ny_IRV_Boulder2023_Test_Canonical_List.csv | 6 - ...andidates-ten-votes-inconsistent-types.sql | 6 +- 19 files changed, 127 insertions(+), 53 deletions(-) rename server/eclipse-project/src/test/resources/CSVs/PluralityOnly/{ThreeCandidatesTenVotes_InsufficientManifest.csv => HundredVotes_DoubledManifest.csv} (68%) create mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_DoubledManifest.csv.sha256sum rename server/eclipse-project/src/test/resources/CSVs/PluralityOnly/{ThreeCandidatesTenVotes_DoubledManifest.csv => HundredVotes_InsufficientManifest.csv} (69%) rename server/eclipse-project/src/test/resources/CSVs/PluralityOnly/{ThreeCandidatesTenVotes_Manifest.csv => HundredVotes_Manifest.csv} (68%) create mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_Manifest.csv.sha256sum rename server/eclipse-project/src/test/resources/CSVs/PluralityOnly/{ThreeCandidatesTenVotes_OneAndAHalfManifest.csv => HundredVotes_OneAndAHalfManifest.csv} (68%) create mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_OneAndAHalfManifest.csv.sha256sum create mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10Margins.csv create mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10Margins.csv.sha256sum delete mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality1And2.csv delete mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality1And2.csv.sha256sum delete mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/PluralityTiedAnd2.csv delete mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/PluralityTiedAnd2.csv.sha256sum create mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality_Only_Test_Canonical_List.csv delete mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_Manifest.csv.sha256sum delete mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Tiny_IRV_Boulder2023_Test_Canonical_List.csv diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java index 37798de4..f7991324 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java @@ -43,7 +43,7 @@ * A demonstration workflow that uploads CVRs and ballot manifests for all 64 counties. * This assumes that main is running. */ -@Test(enabled=false) +@Test(enabled=true) public class Demo1 extends Workflow { /** @@ -85,7 +85,7 @@ public static void beforeAll() { * - demo1_loadCVRs, demo1_loadManifests, * - Boulder_loadCVRs, Boulder_loadManifest. */ - @Test(enabled = false) + @Test(enabled = true) public void runDemo1(){ diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java index 9d71a556..07da75d7 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java @@ -52,7 +52,7 @@ * Note that it does not test for correct sample sizes, only for correct _changes_ to the sample * sizes as a consequence of changes to the manifest. */ -@Test(enabled=false) +@Test(enabled=true) public class EstimateSampleSizesVaryingManifests extends Workflow { /** @@ -91,11 +91,11 @@ public static void beforeAll() { /** * This "test" uploads CVRs and ballot manifests. */ - @Test(enabled = false) + @Test(enabled = true) public void runManifestVaryingDemo() { List CVRS = new ArrayList<>(); - CVRS.add(dataPath + "Plurality1And2.csv"); + CVRS.add(dataPath + "Plurality100votes2And10Margins.csv"); // Upload the CSVs but not the manifests. @@ -107,7 +107,7 @@ public void runManifestVaryingDemo() { // Set the audit info, including the canonical list and the (stupidly large) risk limit; sanity check. final BigDecimal riskLimit = BigDecimal.valueOf(0.5); - updateAuditInfo(dataPath + "Tiny_IRV_Boulder2023_Test_Canonical_List.csv", riskLimit); + updateAuditInfo(dataPath + "Plurality_Only_Test_Canonical_List.csv", riskLimit); dashboard = getDoSDashBoardRefreshResponse(); assertEquals(0, riskLimit .compareTo(new BigDecimal(dashboard.get("audit_info.risk_limit").toString()))); @@ -118,7 +118,7 @@ public void runManifestVaryingDemo() { // 2. Upload the manifest that matches the CSV count, then get estimates. // All the estimate data should be the same. - String matchingManifest = dataPath + "ThreeCandidatesTenVotes_Manifest.csv"; + String matchingManifest = dataPath + "HundredVotes_Manifest.csv"; uploadCounty(1, "ballot-manifest", matchingManifest, matchingManifest + ".sha256sum"); List estimatesWithMatchingManifests = getSampleSizeEstimates(); assertEquals(estimatesWithMatchingManifests.get(0), estimatesWithoutManifests.get(0)); @@ -126,12 +126,12 @@ public void runManifestVaryingDemo() { // 3. Upload the manifest that claims double the CSV count. This should double the sample size // estimate for Plurality2, but not for Plurality1 because it is already topped out at 10. - String doubledManifest = dataPath + "ThreeCandidatesTenVotes_DoubledManifest.csv"; + String doubledManifest = dataPath + "HundredVotes_DoubledManifest.csv"; uploadCounty(1, "ballot-manifest", doubledManifest, doubledManifest + ".sha256sum"); List estimatesWithDoubledManifests = getSampleSizeEstimates(); assertEquals(estimatesWithDoubledManifests.size(), 2); int plurality2index = 1; - if(estimatesWithDoubledManifests.get(0).contestName().equals("PluralityExample2")) { + if(estimatesWithDoubledManifests.get(0).contestName().equals("PluralitMargin2")) { plurality2index = 0; } assertEquals(estimatesWithoutManifests.get(1-plurality2index), diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_InsufficientManifest.csv b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_DoubledManifest.csv similarity index 68% rename from server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_InsufficientManifest.csv rename to server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_DoubledManifest.csv index e2446120..53d773c2 100644 --- a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_InsufficientManifest.csv +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_DoubledManifest.csv @@ -1,2 +1,2 @@ CountyID,ScannerID,BatchID,NumBallots,StorageLocation -TestCounty,1,1,5,Bin 1 +TestCounty,1,1,200,Bin 1 diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_DoubledManifest.csv.sha256sum b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_DoubledManifest.csv.sha256sum new file mode 100644 index 00000000..734acd10 --- /dev/null +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_DoubledManifest.csv.sha256sum @@ -0,0 +1 @@ +de8fd74f9615c9332cb43683bd835c69d1a992f5a944a7610528b354239171fb diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_DoubledManifest.csv b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_InsufficientManifest.csv similarity index 69% rename from server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_DoubledManifest.csv rename to server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_InsufficientManifest.csv index 1c2f4c4d..75d454cf 100644 --- a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_DoubledManifest.csv +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_InsufficientManifest.csv @@ -1,2 +1,2 @@ CountyID,ScannerID,BatchID,NumBallots,StorageLocation -TestCounty,1,1,20,Bin 1 +TestCounty,1,1,50,Bin 1 diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_Manifest.csv b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_Manifest.csv similarity index 68% rename from server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_Manifest.csv rename to server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_Manifest.csv index a776e3a4..b81216db 100644 --- a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_Manifest.csv +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_Manifest.csv @@ -1,2 +1,2 @@ CountyID,ScannerID,BatchID,NumBallots,StorageLocation -TestCounty,1,1,10,Bin 1 +TestCounty,1,1,100,Bin 1 diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_Manifest.csv.sha256sum b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_Manifest.csv.sha256sum new file mode 100644 index 00000000..eb454469 --- /dev/null +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_Manifest.csv.sha256sum @@ -0,0 +1 @@ +236a3cb28077c438b053cc7f0a605b6c8634bac7abff4bd15e2c08de7daf6f61 diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_OneAndAHalfManifest.csv b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_OneAndAHalfManifest.csv similarity index 68% rename from server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_OneAndAHalfManifest.csv rename to server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_OneAndAHalfManifest.csv index 3d759935..2cb9f2b7 100644 --- a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_OneAndAHalfManifest.csv +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_OneAndAHalfManifest.csv @@ -1,2 +1,2 @@ CountyID,ScannerID,BatchID,NumBallots,StorageLocation -TestCounty,1,1,15,Bin 1 +TestCounty,1,1,150,Bin 1 diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_OneAndAHalfManifest.csv.sha256sum b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_OneAndAHalfManifest.csv.sha256sum new file mode 100644 index 00000000..09ddb9ec --- /dev/null +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_OneAndAHalfManifest.csv.sha256sum @@ -0,0 +1 @@ +7cff756072e15d732465d754f8a430e4af18bad24af1cc604f31e455b4d630de diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10Margins.csv b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10Margins.csv new file mode 100644 index 00000000..b39ca9cc --- /dev/null +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10Margins.csv @@ -0,0 +1,104 @@ +Plurality-Margins2-and-10,5.10.11.24,,,,,,,,,,, +,,,,,,,PluralityMargin2 (Vote For=1),PluralityMargin2 (Vote For=1),PluralityMargin2 (Vote For=1),PluralityMargin10 (Vote For=2),PluralityMargin10 (Vote For=2),PluralityMargin10 (Vote For=2) +,,,,,,,Diego,Eli,Farhad,Gertrude,Ho,Imogen +CvrNumber,TabulatorNum,BatchId,RecordId,ImprintedId,PrecinctPortion,BallotType,,,,,, +1,1,1,1,1-1-1,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,1 +2,1,1,2,1-1-2,Precinct 1,Ballot 1 - Type 1,1,0,0,0,0,1 +3,1,1,3,1-1-3,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 +4,1,1,4,1-1-4,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 +5,1,1,5,1-1-5,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +6,1,1,6,1-1-6,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +7,1,1,7,1-1-7,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +8,1,1,8,1-1-8,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +9,1,1,9,1-1-9,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +10,1,1,10,1-1-10,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +11,1,1,11,1-1-11,Precinct 1,Ballot 1 - Type 1,0,0,0,1,0,1 +12,1,1,12,1-1-12,Precinct 1,Ballot 1 - Type 1,0,0,1,0,0,1 +13,1,1,13,1-1-13,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 +14,1,1,14,1-1-14,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 +15,1,1,15,1-1-15,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +16,1,1,16,1-1-16,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +17,1,1,17,1-1-17,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +18,1,1,18,1-1-18,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +19,1,1,19,1-1-19,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +20,1,1,20,1-1-20,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +21,1,1,21,1-1-21,Precinct 1,Ballot 1 - Type 1,0,0,0,1,0,1 +22,1,1,22,1-1-22,Precinct 1,Ballot 1 - Type 1,0,0,1,0,0,1 +23,1,1,23,1-1-23,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 +24,1,1,24,1-1-24,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 +25,1,1,25,1-1-25,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +26,1,1,26,1-1-26,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +27,1,1,27,1-1-27,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +28,1,1,28,1-1-28,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +29,1,1,29,1-1-29,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +30,1,1,30,1-1-30,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +31,1,1,31,1-1-31,Precinct 1,Ballot 1 - Type 1,0,0,0,1,0,1 +32,1,1,32,1-1-32,Precinct 1,Ballot 1 - Type 1,0,0,1,0,0,1 +33,1,1,33,1-1-33,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 +34,1,1,34,1-1-34,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 +35,1,1,35,1-1-35,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +36,1,1,36,1-1-36,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +37,1,1,37,1-1-37,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +38,1,1,38,1-1-38,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +39,1,1,39,1-1-39,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +40,1,1,40,1-1-40,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +41,1,1,41,1-1-41,Precinct 1,Ballot 1 - Type 1,0,0,0,1,0,1 +42,1,1,42,1-1-42,Precinct 1,Ballot 1 - Type 1,0,0,1,0,0,1 +43,1,1,43,1-1-43,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 +44,1,1,44,1-1-44,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 +45,1,1,45,1-1-45,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +46,1,1,46,1-1-46,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +47,1,1,47,1-1-47,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +48,1,1,48,1-1-48,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +49,1,1,49,1-1-49,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +50,1,1,50,1-1-50,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +51,1,1,51,1-1-51,Precinct 1,Ballot 1 - Type 1,0,0,0,1,0,1 +52,1,1,52,1-1-52,Precinct 1,Ballot 1 - Type 1,0,0,1,0,0,1 +53,1,1,53,1-1-53,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 +54,1,1,54,1-1-54,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 +55,1,1,55,1-1-55,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +56,1,1,56,1-1-56,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +57,1,1,57,1-1-57,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +58,1,1,58,1-1-58,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +59,1,1,59,1-1-59,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +60,1,1,60,1-1-60,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +61,1,1,61,1-1-61,Precinct 1,Ballot 1 - Type 1,0,0,0,1,0,1 +62,1,1,62,1-1-62,Precinct 1,Ballot 1 - Type 1,0,0,1,0,0,1 +63,1,1,63,1-1-63,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 +64,1,1,64,1-1-64,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 +65,1,1,65,1-1-65,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +66,1,1,66,1-1-66,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +67,1,1,67,1-1-67,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +68,1,1,68,1-1-68,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +69,1,1,69,1-1-69,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +70,1,1,70,1-1-70,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +71,1,1,71,1-1-71,Precinct 1,Ballot 1 - Type 1,0,0,0,1,0,1 +72,1,1,72,1-1-72,Precinct 1,Ballot 1 - Type 1,0,0,1,0,0,1 +73,1,1,73,1-1-73,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 +74,1,1,74,1-1-74,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 +75,1,1,75,1-1-75,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +76,1,1,76,1-1-76,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +77,1,1,77,1-1-77,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +78,1,1,78,1-1-78,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +79,1,1,79,1-1-79,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +80,1,1,80,1-1-80,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +81,1,1,81,1-1-81,Precinct 1,Ballot 1 - Type 1,0,0,0,1,0,1 +82,1,1,82,1-1-82,Precinct 1,Ballot 1 - Type 1,0,0,1,0,0,1 +83,1,1,83,1-1-83,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 +84,1,1,84,1-1-84,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 +85,1,1,85,1-1-85,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +86,1,1,86,1-1-86,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +87,1,1,87,1-1-87,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +88,1,1,88,1-1-88,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +89,1,1,89,1-1-89,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +90,1,1,90,1-1-90,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +91,1,1,91,1-1-91,Precinct 1,Ballot 1 - Type 1,0,0,0,1,0,1 +92,1,1,92,1-1-92,Precinct 1,Ballot 1 - Type 1,0,0,1,0,0,1 +93,1,1,93,1-1-93,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 +94,1,1,94,1-1-94,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 +95,1,1,95,1-1-95,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +96,1,1,96,1-1-96,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +97,1,1,97,1-1-97,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 +98,1,1,98,1-1-98,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +99,1,1,99,1-1-99,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +100,1,1,100,1-1-100,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10Margins.csv.sha256sum b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10Margins.csv.sha256sum new file mode 100644 index 00000000..b3cb4721 --- /dev/null +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10Margins.csv.sha256sum @@ -0,0 +1 @@ +c1bfa16c297ba4bd470145f1b4f25dfc972d47ef511d28a458b8e1349af78e2a diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality1And2.csv b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality1And2.csv deleted file mode 100644 index bf588d2f..00000000 --- a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality1And2.csv +++ /dev/null @@ -1,14 +0,0 @@ -Tiny Plurality Example Contest 1,5.10.11.24,,,,,,,,,,, -,,,,,,,PluralityExample1 (Vote For=1),PluralityExample1 (Vote For=1),PluralityExample1 (Vote For=1),PluralityExample2 (Vote For=2),PluralityExample2 (Vote For=2),PluralityExample2 (Vote For=2) -,,,,,,,Diego,Eli,Farhad,Gertrude,Ho,Imogen -CvrNumber,TabulatorNum,BatchId,RecordId,ImprintedId,PrecinctPortion,BallotType,,,,,, -1,1,1,1,1-1-1,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,1 -2,1,1,2,1-1-2,Precinct 1,Ballot 1 - Type 1,0,0,1,0,0,1 -3,1,1,3,1-1-3,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -4,1,1,4,1-1-4,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -5,1,1,5,1-1-5,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -6,1,1,6,1-1-6,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -7,1,1,7,1-1-7,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -8,1,1,8,1-1-8,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -9,1,1,9,1-1-9,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -10,1,1,10,1-1-10,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality1And2.csv.sha256sum b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality1And2.csv.sha256sum deleted file mode 100644 index 972b7e89..00000000 --- a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality1And2.csv.sha256sum +++ /dev/null @@ -1 +0,0 @@ -4b71abe4140d87d68afd5741e84756f999307cb7e6ba1eb0843846cf9c3527f3 diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/PluralityTiedAnd2.csv b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/PluralityTiedAnd2.csv deleted file mode 100644 index 7c39ea3d..00000000 --- a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/PluralityTiedAnd2.csv +++ /dev/null @@ -1,14 +0,0 @@ -Tiny IRV Example Contest 1,5.10.11.24,,,,,,,,,,, -,,,,,,,PluralityTiedExample (Vote For=1),PluralityTiedExample (Vote For=1),PluralityTiedExample (Vote For=1),PluralityExample2 (Vote For=2),PluralityExample2 (Vote For=2),PluralityExample2 (Vote For=2) -,,,,,,,Diego,Eli,Farhad,Gertrude,Ho,Imogen -CvrNumber,TabulatorNum,BatchId,RecordId,ImprintedId,PrecinctPortion,BallotType,,,,,, -1,1,1,1,1-1-1,Precinct 1,Ballot 1 - Type 1,0,0,0,1,0,1 -2,1,1,2,1-1-2,Precinct 1,Ballot 1 - Type 1,0,0,1,0,0,1 -3,1,1,3,1-1-3,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -4,1,1,4,1-1-4,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -5,1,1,5,1-1-5,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -6,1,1,6,1-1-6,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -7,1,1,7,1-1-7,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -8,1,1,8,1-1-8,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -9,1,1,9,1-1-9,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -10,1,1,10,1-1-10,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/PluralityTiedAnd2.csv.sha256sum b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/PluralityTiedAnd2.csv.sha256sum deleted file mode 100644 index 14b03741..00000000 --- a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/PluralityTiedAnd2.csv.sha256sum +++ /dev/null @@ -1 +0,0 @@ -9d334367af068d83681f3658250567066bb1490c92dd2d387a7ef26ac80bbfd5 diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality_Only_Test_Canonical_List.csv b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality_Only_Test_Canonical_List.csv new file mode 100644 index 00000000..08137201 --- /dev/null +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality_Only_Test_Canonical_List.csv @@ -0,0 +1,3 @@ +CountyName,ContestName,ContestChoices +Adams,PluralityMargin2,"Diego, Eli, Farhad" +Adams,PluralityMargin10,"Gertrude, Ho, Imogen" diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_Manifest.csv.sha256sum b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_Manifest.csv.sha256sum deleted file mode 100644 index a8db7e94..00000000 --- a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/ThreeCandidatesTenVotes_Manifest.csv.sha256sum +++ /dev/null @@ -1 +0,0 @@ -5d602a0a0b2bc51a2e9f1f6efd8650fdd748e4fdd8268104a43a633588a998c3 diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Tiny_IRV_Boulder2023_Test_Canonical_List.csv b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Tiny_IRV_Boulder2023_Test_Canonical_List.csv deleted file mode 100644 index b265e683..00000000 --- a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Tiny_IRV_Boulder2023_Test_Canonical_List.csv +++ /dev/null @@ -1,6 +0,0 @@ -CountyName,ContestName,ContestChoices -Boulder,City of Boulder Mayoral,"Aaron Brockett,Nicole Speer,Bob Yates,Paul Tweedlie," -Adams,TinyExample1,"Alice, Bob, Chuan" -Adams,PluralityExample1,"Diego, Eli, Farhad" -Adams,PluralityExample2,"Gertrude, Ho, Imogen" -Adams,Example3,"A,B,C,D" diff --git a/server/eclipse-project/src/test/resources/SQL/corla-three-candidates-ten-votes-inconsistent-types.sql b/server/eclipse-project/src/test/resources/SQL/corla-three-candidates-ten-votes-inconsistent-types.sql index b338e0a0..a063c735 100644 --- a/server/eclipse-project/src/test/resources/SQL/corla-three-candidates-ten-votes-inconsistent-types.sql +++ b/server/eclipse-project/src/test/resources/SQL/corla-three-candidates-ten-votes-inconsistent-types.sql @@ -193,8 +193,8 @@ INSERT INTO public.cvr_contest_info (cvr_id, county_id, choices, comment, consen -- INSERT INTO public.uploaded_file (id, computed_hash, approximate_record_count, file, filename, size, "timestamp", version, result, status, submitted_hash, county_id) VALUES (240488, '7760C6690CAACD2568CA80ED10B8D3362D561D4573890DA867B2CAC10EE89659', 14, 17313, 'ThreeCandidatesTenVotesPlusPlurality.csv', 1718, '2024-06-13 19:42:48.902499', 1, '{"success":true,"importedCount":10}', 'IMPORTED', '7760C6690CAACD2568CA80ED10B8D3362D561D4573890DA867B2CAC10EE89659', 1); -INSERT INTO public.uploaded_file (id, computed_hash, approximate_record_count, file, filename, size, "timestamp", version, result, status, submitted_hash, county_id) VALUES (240543, '5D602A0A0B2BC51A2E9F1F6EFD8650FDD748E4FDD8268104A43A633588A998C3', 2, 17314, 'ThreeCandidatesTenVotes_Manifest.csv', 78, '2024-06-13 19:43:21.05893', 1, '{"success":true,"importedCount":1,"errorMessage":null,"errorRowNum":null,"errorRowContent":null}', 'IMPORTED', '5D602A0A0B2BC51A2E9F1F6EFD8650FDD748E4FDD8268104A43A633588A998C3', 1); -INSERT INTO public.uploaded_file (id, computed_hash, approximate_record_count, file, filename, size, "timestamp", version, result, status, submitted_hash, county_id) VALUES (240590, '5D602A0A0B2BC51A2E9F1F6EFD8650FDD748E4FDD8268104A43A633588A998C3', 2, 17316, 'ThreeCandidatesTenVotes_Manifest.csv', 78, '2024-06-13 19:44:06.224713', 1, '{"success":true,"importedCount":1,"errorMessage":null,"errorRowNum":null,"errorRowContent":null}', 'IMPORTED', '5D602A0A0B2BC51A2E9F1F6EFD8650FDD748E4FDD8268104A43A633588A998C3', 2); +INSERT INTO public.uploaded_file (id, computed_hash, approximate_record_count, file, filename, size, "timestamp", version, result, status, submitted_hash, county_id) VALUES (240543, '5D602A0A0B2BC51A2E9F1F6EFD8650FDD748E4FDD8268104A43A633588A998C3', 2, 17314, 'HundredVotes_Manifest.csv', 78, '2024-06-13 19:43:21.05893', 1, '{"success":true,"importedCount":1,"errorMessage":null,"errorRowNum":null,"errorRowContent":null}', 'IMPORTED', '5D602A0A0B2BC51A2E9F1F6EFD8650FDD748E4FDD8268104A43A633588A998C3', 1); +INSERT INTO public.uploaded_file (id, computed_hash, approximate_record_count, file, filename, size, "timestamp", version, result, status, submitted_hash, county_id) VALUES (240590, '5D602A0A0B2BC51A2E9F1F6EFD8650FDD748E4FDD8268104A43A633588A998C3', 2, 17316, 'HundredVotes_Manifest.csv', 78, '2024-06-13 19:44:06.224713', 1, '{"success":true,"importedCount":1,"errorMessage":null,"errorRowNum":null,"errorRowContent":null}', 'IMPORTED', '5D602A0A0B2BC51A2E9F1F6EFD8650FDD748E4FDD8268104A43A633588A998C3', 2); INSERT INTO public.uploaded_file (id, computed_hash, approximate_record_count, file, filename, size, "timestamp", version, result, status, submitted_hash, county_id) VALUES (240618, 'D2EE3F29D1CCB8B0F4B790493EDC2F41B4081DCC1D760277ACA27268140D166C', 14, 17317, 'ThreeCandidatesTenVotes.csv', 1364, '2024-06-13 19:44:27.083812', 1, '{"success":true,"importedCount":10}', 'IMPORTED', 'D2EE3F29D1CCB8B0F4B790493EDC2F41B4081DCC1D760277ACA27268140D166C', 2); INSERT INTO public.uploaded_file (id, computed_hash, approximate_record_count, file, filename, size, "timestamp", version, result, status, submitted_hash, county_id) VALUES (241040, 'ECF373ABB783C2A6577FD46BF2437C728498E88304A6FC2CCEFB61A1082B3556', 14, 17322, 'ThreeCandidatesTenVotesPlusInconsistentPlurality.csv', 2389, '2024-06-13 19:53:11.723821', 1, '{"success":true,"importedCount":10}', 'IMPORTED', 'ECF373ABB783C2A6577FD46BF2437C728498E88304A6FC2CCEFB61A1082B3556', 3); -INSERT INTO public.uploaded_file (id, computed_hash, approximate_record_count, file, filename, size, "timestamp", version, result, status, submitted_hash, county_id) VALUES (241095, '5D602A0A0B2BC51A2E9F1F6EFD8650FDD748E4FDD8268104A43A633588A998C3', 2, 17324, 'ThreeCandidatesTenVotes_Manifest.csv', 78, '2024-06-13 19:53:49.606724', 1, '{"success":true,"importedCount":1,"errorMessage":null,"errorRowNum":null,"errorRowContent":null}', 'IMPORTED', '5D602A0A0B2BC51A2E9F1F6EFD8650FDD748E4FDD8268104A43A633588A998C3', 3); \ No newline at end of file +INSERT INTO public.uploaded_file (id, computed_hash, approximate_record_count, file, filename, size, "timestamp", version, result, status, submitted_hash, county_id) VALUES (241095, '5D602A0A0B2BC51A2E9F1F6EFD8650FDD748E4FDD8268104A43A633588A998C3', 2, 17324, 'HundredVotes_Manifest.csv', 78, '2024-06-13 19:53:49.606724', 1, '{"success":true,"importedCount":1,"errorMessage":null,"errorRowNum":null,"errorRowContent":null}', 'IMPORTED', '5D602A0A0B2BC51A2E9F1F6EFD8650FDD748E4FDD8268104A43A633588A998C3', 3); \ No newline at end of file From a9c92afa3be1a5767a5f6079ee711a2489f5ab3b Mon Sep 17 00:00:00 2001 From: vteague Date: Fri, 8 Nov 2024 22:42:09 +1100 Subject: [PATCH 25/40] Getting corla's sample size estimates after audit round started. --- .../corla/workflows/Demo1.java | 7 +- .../EstimateSampleSizesVaryingManifests.java | 43 ++-- .../corla/workflows/Workflow.java | 26 ++- .../Plurality100votes2And10Margins.csv | 200 +++++++++--------- ...urality100votes2And10Margins.csv.sha256sum | 2 +- 5 files changed, 151 insertions(+), 127 deletions(-) diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java index f7991324..254c5587 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java @@ -157,16 +157,15 @@ public void runDemo1(){ targetContests(Map.of("City of Longmont - Mayor","COUNTY_WIDE_CONTEST")); // 6. Set the seed. - final String seed = "9823749812374981273489712389471238974"; - setSeed(seed); + setSeed(defaultSeed); // This should be complete audit info. dashboard = getDoSDashBoardRefreshResponse(); - assertEquals(dashboard.get("audit_info.seed"), seed); + assertEquals(dashboard.get("audit_info.seed"), defaultSeed); assertEquals(dashboard.get("asm_state"), COMPLETE_AUDIT_INFO_SET.toString()); // 7. Estimate sample sizes; sanity check. - List sampleSizes = getSampleSizeEstimates(); + Map sampleSizes = getSampleSizeEstimates(); assertFalse(sampleSizes.isEmpty()); // TODO get assertions, sanity check. diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java index 07da75d7..13cb6235 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java @@ -36,7 +36,6 @@ import java.math.BigDecimal; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -48,7 +47,6 @@ * increase the sample size, and a smaller manifest than CVR list should throw an error. * All the tests here are the same plain plurality contest - we just vary the absence of presence of * the manifest, and (if present) the number of ballots it states. - * * Note that it does not test for correct sample sizes, only for correct _changes_ to the sample * sizes as a consequence of changes to the manifest. */ @@ -96,6 +94,8 @@ public void runManifestVaryingDemo() { List CVRS = new ArrayList<>(); CVRS.add(dataPath + "Plurality100votes2And10Margins.csv"); + final String margin2Contest = "PluralityMargin2"; + final String margin10Contest = "PluralityMargin10"; // Upload the CSVs but not the manifests. @@ -108,36 +108,45 @@ public void runManifestVaryingDemo() { // Set the audit info, including the canonical list and the (stupidly large) risk limit; sanity check. final BigDecimal riskLimit = BigDecimal.valueOf(0.5); updateAuditInfo(dataPath + "Plurality_Only_Test_Canonical_List.csv", riskLimit); + setSeed(defaultSeed); dashboard = getDoSDashBoardRefreshResponse(); assertEquals(0, riskLimit .compareTo(new BigDecimal(dashboard.get("audit_info.risk_limit").toString()))); // 1. Estimate Sample sizes, without the manifest. This should count the CSVs. - List estimatesWithoutManifests = getSampleSizeEstimates(); + Map estimatesWithoutManifests = getSampleSizeEstimates(); assertEquals(estimatesWithoutManifests.size(), 2); // 2. Upload the manifest that matches the CSV count, then get estimates. - // All the estimate data should be the same. + // All the estimate data should be the same, because EstimateSampleSizes doesn't use manifests. + // TODO do this with doubled manifests too. String matchingManifest = dataPath + "HundredVotes_Manifest.csv"; uploadCounty(1, "ballot-manifest", matchingManifest, matchingManifest + ".sha256sum"); - List estimatesWithMatchingManifests = getSampleSizeEstimates(); - assertEquals(estimatesWithMatchingManifests.get(0), estimatesWithoutManifests.get(0)); - assertEquals(estimatesWithMatchingManifests.get(1), estimatesWithoutManifests.get(1)); + // TODO targetContests doesn't work without manifests (which is good) but also doesn't seem to throw an error (though it should). + targetContests(Map.of(margin2Contest, "COUNTY_WIDE_CONTEST", margin10Contest,"COUNTY_WIDE_CONTEST")); + Map estimatesWithMatchingManifests = getSampleSizeEstimates(); + dashboard = getDoSDashBoardRefreshResponse(); + assertEquals(estimatesWithMatchingManifests.get(margin2Contest), estimatesWithoutManifests.get(margin2Contest)); + assertEquals(estimatesWithMatchingManifests.get(margin10Contest), estimatesWithoutManifests.get(margin10Contest)); + + // Check that the estimatedSampleSizes inside corla also match the pre-audit estimates. + // They should, because the manifests and CVR files have equal counts. + startAuditRound(); + dashboard = getDoSDashBoardRefreshResponse(); + int margin2Estimate = dashboard.getInt("county_status.1.estimated_ballots_to_audit"); + int margin10Estimate = dashboard.getInt("county_status.1.estimated_ballots_to_audit"); // 3. Upload the manifest that claims double the CSV count. This should double the sample size - // estimate for Plurality2, but not for Plurality1 because it is already topped out at 10. + // estimates. String doubledManifest = dataPath + "HundredVotes_DoubledManifest.csv"; uploadCounty(1, "ballot-manifest", doubledManifest, doubledManifest + ".sha256sum"); - List estimatesWithDoubledManifests = getSampleSizeEstimates(); + Map estimatesWithDoubledManifests = getSampleSizeEstimates(); + dashboard = getDoSDashBoardRefreshResponse(); assertEquals(estimatesWithDoubledManifests.size(), 2); - int plurality2index = 1; - if(estimatesWithDoubledManifests.get(0).contestName().equals("PluralitMargin2")) { - plurality2index = 0; - } - assertEquals(estimatesWithoutManifests.get(1-plurality2index), - estimatesWithDoubledManifests.get(1-plurality2index)); - assertEquals(estimatesWithoutManifests.get(plurality2index).estimatedSamples(), - estimatesWithDoubledManifests.get(plurality2index).estimatedSamples()/2); + assertEquals(estimatesWithoutManifests.get(margin2Contest).estimatedSamples(), + estimatesWithDoubledManifests.get(margin2Contest).estimatedSamples()/2); + assertEquals(estimatesWithoutManifests.get(margin10Contest).estimatedSamples(), + estimatesWithDoubledManifests.get(margin10Contest).estimatedSamples()/2); } } diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java index 14e2aed1..ae35d464 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java @@ -43,7 +43,6 @@ import org.apache.log4j.Logger; import org.apache.http.HttpStatus; import org.testng.annotations.BeforeClass; -import us.freeandfair.corla.model.Contest; import wiremock.net.minidev.json.JSONArray; import wiremock.net.minidev.json.JSONObject; @@ -58,6 +57,11 @@ public class Workflow extends TestClassWithDatabase { */ protected static final int numCounties = 64; + /** + * Default PRNG seed. + */ + protected static final String defaultSeed = "24098249082409821390482049098"; + /** * Class-wide logger */ @@ -213,7 +217,7 @@ protected JsonPath getDoSDashBoardRefreshResponse() { * Get the sample size estimates CSV and return the parsed data. * @return The sample size estimate data as a list of EstimateData structures. */ - protected List getSampleSizeEstimates() { + protected Map getSampleSizeEstimates() { final String prefix = "[getSampleSizeEstimates]"; // Login as state admin. @@ -229,7 +233,7 @@ protected List getSampleSizeEstimates() { .body() .asString(); - List estimates = new ArrayList<>(); + Map estimates = new HashMap<>(); var lines = data.split("\n"); // Skip the first line (which has headers) for(int i = 1 ; i < lines.length ; i++) { @@ -250,7 +254,7 @@ protected List getSampleSizeEstimates() { new BigDecimal(line[5]), Integer.parseInt(line[6]) ); - estimates.add(estimate); + estimates.put(estimate.contestName(), estimate); } return estimates; @@ -288,7 +292,8 @@ protected void updateAuditInfo(String canonicalListFile, BigDecimal riskLimit) { /** * Generate assertions (for IRV contests) - * TODO At the moment this expects the raire-service to be running. + * TODO At the moment this expects the raire-service to be running, which is a problem because it will be reading the + * wrong database. * Set it up so that we run raire-service inside the Docker container and tell main where to find it. */ protected void generateAssertions(double timeLimitSeconds) { @@ -304,6 +309,17 @@ protected void generateAssertions(double timeLimitSeconds) { .statusCode(HttpStatus.SC_OK); } + protected void startAuditRound() { + // Login as state admin. + final SessionFilter filter = doLogin("stateadmin1"); + + given() + .filter(filter) + .post("/start-audit-round") + .then() + .assertThat() + .statusCode(HttpStatus.SC_OK); + } /** * Select contests to target, by name. */ diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10Margins.csv b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10Margins.csv index b39ca9cc..eee1d35b 100644 --- a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10Margins.csv +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10Margins.csv @@ -2,103 +2,103 @@ Plurality-Margins2-and-10,5.10.11.24,,,,,,,,,,, ,,,,,,,PluralityMargin2 (Vote For=1),PluralityMargin2 (Vote For=1),PluralityMargin2 (Vote For=1),PluralityMargin10 (Vote For=2),PluralityMargin10 (Vote For=2),PluralityMargin10 (Vote For=2) ,,,,,,,Diego,Eli,Farhad,Gertrude,Ho,Imogen CvrNumber,TabulatorNum,BatchId,RecordId,ImprintedId,PrecinctPortion,BallotType,,,,,, -1,1,1,1,1-1-1,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,1 -2,1,1,2,1-1-2,Precinct 1,Ballot 1 - Type 1,1,0,0,0,0,1 -3,1,1,3,1-1-3,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -4,1,1,4,1-1-4,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -5,1,1,5,1-1-5,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -6,1,1,6,1-1-6,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -7,1,1,7,1-1-7,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -8,1,1,8,1-1-8,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -9,1,1,9,1-1-9,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -10,1,1,10,1-1-10,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -11,1,1,11,1-1-11,Precinct 1,Ballot 1 - Type 1,0,0,0,1,0,1 -12,1,1,12,1-1-12,Precinct 1,Ballot 1 - Type 1,0,0,1,0,0,1 -13,1,1,13,1-1-13,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -14,1,1,14,1-1-14,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -15,1,1,15,1-1-15,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -16,1,1,16,1-1-16,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -17,1,1,17,1-1-17,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -18,1,1,18,1-1-18,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -19,1,1,19,1-1-19,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -20,1,1,20,1-1-20,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -21,1,1,21,1-1-21,Precinct 1,Ballot 1 - Type 1,0,0,0,1,0,1 -22,1,1,22,1-1-22,Precinct 1,Ballot 1 - Type 1,0,0,1,0,0,1 -23,1,1,23,1-1-23,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -24,1,1,24,1-1-24,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -25,1,1,25,1-1-25,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -26,1,1,26,1-1-26,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -27,1,1,27,1-1-27,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -28,1,1,28,1-1-28,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -29,1,1,29,1-1-29,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -30,1,1,30,1-1-30,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -31,1,1,31,1-1-31,Precinct 1,Ballot 1 - Type 1,0,0,0,1,0,1 -32,1,1,32,1-1-32,Precinct 1,Ballot 1 - Type 1,0,0,1,0,0,1 -33,1,1,33,1-1-33,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -34,1,1,34,1-1-34,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -35,1,1,35,1-1-35,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -36,1,1,36,1-1-36,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -37,1,1,37,1-1-37,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -38,1,1,38,1-1-38,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -39,1,1,39,1-1-39,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -40,1,1,40,1-1-40,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -41,1,1,41,1-1-41,Precinct 1,Ballot 1 - Type 1,0,0,0,1,0,1 -42,1,1,42,1-1-42,Precinct 1,Ballot 1 - Type 1,0,0,1,0,0,1 -43,1,1,43,1-1-43,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -44,1,1,44,1-1-44,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -45,1,1,45,1-1-45,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -46,1,1,46,1-1-46,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -47,1,1,47,1-1-47,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -48,1,1,48,1-1-48,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -49,1,1,49,1-1-49,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -50,1,1,50,1-1-50,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -51,1,1,51,1-1-51,Precinct 1,Ballot 1 - Type 1,0,0,0,1,0,1 -52,1,1,52,1-1-52,Precinct 1,Ballot 1 - Type 1,0,0,1,0,0,1 -53,1,1,53,1-1-53,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -54,1,1,54,1-1-54,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -55,1,1,55,1-1-55,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -56,1,1,56,1-1-56,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -57,1,1,57,1-1-57,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -58,1,1,58,1-1-58,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -59,1,1,59,1-1-59,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -60,1,1,60,1-1-60,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -61,1,1,61,1-1-61,Precinct 1,Ballot 1 - Type 1,0,0,0,1,0,1 -62,1,1,62,1-1-62,Precinct 1,Ballot 1 - Type 1,0,0,1,0,0,1 -63,1,1,63,1-1-63,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -64,1,1,64,1-1-64,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -65,1,1,65,1-1-65,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -66,1,1,66,1-1-66,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -67,1,1,67,1-1-67,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -68,1,1,68,1-1-68,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -69,1,1,69,1-1-69,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -70,1,1,70,1-1-70,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -71,1,1,71,1-1-71,Precinct 1,Ballot 1 - Type 1,0,0,0,1,0,1 -72,1,1,72,1-1-72,Precinct 1,Ballot 1 - Type 1,0,0,1,0,0,1 -73,1,1,73,1-1-73,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -74,1,1,74,1-1-74,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -75,1,1,75,1-1-75,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -76,1,1,76,1-1-76,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -77,1,1,77,1-1-77,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -78,1,1,78,1-1-78,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -79,1,1,79,1-1-79,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -80,1,1,80,1-1-80,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -81,1,1,81,1-1-81,Precinct 1,Ballot 1 - Type 1,0,0,0,1,0,1 -82,1,1,82,1-1-82,Precinct 1,Ballot 1 - Type 1,0,0,1,0,0,1 -83,1,1,83,1-1-83,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -84,1,1,84,1-1-84,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -85,1,1,85,1-1-85,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -86,1,1,86,1-1-86,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -87,1,1,87,1-1-87,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -88,1,1,88,1-1-88,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -89,1,1,89,1-1-89,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -90,1,1,90,1-1-90,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -91,1,1,91,1-1-91,Precinct 1,Ballot 1 - Type 1,0,0,0,1,0,1 -92,1,1,92,1-1-92,Precinct 1,Ballot 1 - Type 1,0,0,1,0,0,1 -93,1,1,93,1-1-93,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -94,1,1,94,1-1-94,Precinct 1,Ballot 1 - Type 1,0,0,1,1,1,0 -95,1,1,95,1-1-95,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -96,1,1,96,1-1-96,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -97,1,1,97,1-1-97,Precinct 1,Ballot 1 - Type 1,1,0,0,1,1,0 -98,1,1,98,1-1-98,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -99,1,1,99,1-1-99,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 -100,1,1,100,1-1-100,Precinct 1,Ballot 1 - Type 1,0,1,0,1,1,0 +1,1,1,1,1-1-1,Precinct 1,Ballot 1 - Type 1,1,0,0,0,1,1 +2,1,1,2,1-1-2,Precinct 1,Ballot 1 - Type 1,1,0,0,0,1,1 +3,1,1,3,1-1-3,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +4,1,1,4,1-1-4,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +5,1,1,5,1-1-5,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +6,1,1,6,1-1-6,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +7,1,1,7,1-1-7,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +8,1,1,8,1-1-8,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +9,1,1,9,1-1-9,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +10,1,1,10,1-1-10,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +11,1,1,11,1-1-11,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +12,1,1,12,1-1-12,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +13,1,1,13,1-1-13,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +14,1,1,14,1-1-14,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +15,1,1,15,1-1-15,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +16,1,1,16,1-1-16,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +17,1,1,17,1-1-17,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +18,1,1,18,1-1-18,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +19,1,1,19,1-1-19,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +20,1,1,20,1-1-20,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +21,1,1,21,1-1-21,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +22,1,1,22,1-1-22,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +23,1,1,23,1-1-23,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +24,1,1,24,1-1-24,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +25,1,1,25,1-1-25,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +26,1,1,26,1-1-26,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +27,1,1,27,1-1-27,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +28,1,1,28,1-1-28,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +29,1,1,29,1-1-29,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +30,1,1,30,1-1-30,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +31,1,1,31,1-1-31,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +32,1,1,32,1-1-32,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +33,1,1,33,1-1-33,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +34,1,1,34,1-1-34,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +35,1,1,35,1-1-35,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +36,1,1,36,1-1-36,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +37,1,1,37,1-1-37,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +38,1,1,38,1-1-38,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +39,1,1,39,1-1-39,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +40,1,1,40,1-1-40,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +41,1,1,41,1-1-41,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +42,1,1,42,1-1-42,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +43,1,1,43,1-1-43,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +44,1,1,44,1-1-44,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +45,1,1,45,1-1-45,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +46,1,1,46,1-1-46,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +47,1,1,47,1-1-47,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +48,1,1,48,1-1-48,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +49,1,1,49,1-1-49,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +50,1,1,50,1-1-50,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +51,1,1,51,1-1-51,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +52,1,1,52,1-1-52,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +53,1,1,53,1-1-53,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +54,1,1,54,1-1-54,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +55,1,1,55,1-1-55,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +56,1,1,56,1-1-56,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +57,1,1,57,1-1-57,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +58,1,1,58,1-1-58,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +59,1,1,59,1-1-59,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +60,1,1,60,1-1-60,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +61,1,1,61,1-1-61,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +62,1,1,62,1-1-62,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +63,1,1,63,1-1-63,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +64,1,1,64,1-1-64,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +65,1,1,65,1-1-65,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +66,1,1,66,1-1-66,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +67,1,1,67,1-1-67,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +68,1,1,68,1-1-68,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +69,1,1,69,1-1-69,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +70,1,1,70,1-1-70,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +71,1,1,71,1-1-71,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +72,1,1,72,1-1-72,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +73,1,1,73,1-1-73,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +74,1,1,74,1-1-74,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +75,1,1,75,1-1-75,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +76,1,1,76,1-1-76,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +77,1,1,77,1-1-77,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +78,1,1,78,1-1-78,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +79,1,1,79,1-1-79,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +80,1,1,80,1-1-80,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +81,1,1,81,1-1-81,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +82,1,1,82,1-1-82,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +83,1,1,83,1-1-83,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +84,1,1,84,1-1-84,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +85,1,1,85,1-1-85,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +86,1,1,86,1-1-86,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +87,1,1,87,1-1-87,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +88,1,1,88,1-1-88,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +89,1,1,89,1-1-89,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +90,1,1,90,1-1-90,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +91,1,1,91,1-1-91,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +92,1,1,92,1-1-92,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +93,1,1,93,1-1-93,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +94,1,1,94,1-1-94,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +95,1,1,95,1-1-95,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +96,1,1,96,1-1-96,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +97,1,1,97,1-1-97,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +98,1,1,98,1-1-98,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +99,1,1,99,1-1-99,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +100,1,1,100,1-1-100,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10Margins.csv.sha256sum b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10Margins.csv.sha256sum index b3cb4721..b27d74f0 100644 --- a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10Margins.csv.sha256sum +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10Margins.csv.sha256sum @@ -1 +1 @@ -c1bfa16c297ba4bd470145f1b4f25dfc972d47ef511d28a458b8e1349af78e2a +e3e84cb72b2fa4e7d30fe58c7d20ed2981c414d4f2362e9b7dc9ad1154c3a588 From 006c655d5271f5377de4925de2244a6a3439d52a Mon Sep 17 00:00:00 2001 From: vteague Date: Sat, 9 Nov 2024 17:24:52 +1100 Subject: [PATCH 26/40] Correct file uploads. --- .../EstimateSampleSizesVaryingManifests.java | 59 +++++++--- ...HundredVotes_DoubledManifest.csv.sha256sum | 2 +- .../HundredVotes_OneAndAHalfManifest.csv | 2 - ...redVotes_OneAndAHalfManifest.csv.sha256sum | 1 - .../Plurality100votes2And10MarginsCopy2.csv | 104 ++++++++++++++++++ .../Plurality100votes2And10MarginsCopy3.csv | 104 ++++++++++++++++++ .../Plurality_Only_Test_Canonical_List.csv | 4 + 7 files changed, 255 insertions(+), 21 deletions(-) delete mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_OneAndAHalfManifest.csv delete mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_OneAndAHalfManifest.csv.sha256sum create mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10MarginsCopy2.csv create mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10MarginsCopy3.csv diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java index 13cb6235..969867e4 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java @@ -92,14 +92,19 @@ public static void beforeAll() { @Test(enabled = true) public void runManifestVaryingDemo() { - List CVRS = new ArrayList<>(); - CVRS.add(dataPath + "Plurality100votes2And10Margins.csv"); final String margin2Contest = "PluralityMargin2"; final String margin10Contest = "PluralityMargin10"; - // Upload the CSVs but not the manifests. - uploadCounty(1, "cvr-export", CVRS.get(0), CVRS.get(0) + ".sha256sum"); + final String CVRFile = dataPath + "Plurality100votes2And10Margins"; + final String[] suffixes = {"", "Copy2", "Copy3"}; + + for(int i = 1 ; i <= 3 ; i++) { + uploadCounty(i, "cvr-export", CVRFile + suffixes[i-1] + ".csv", + CVRFile + suffixes[i-1] + ".csv.sha256sum"); + } + + //TODO figure out how to wait. // Get the DoSDashboard refresh response; sanity check. JsonPath dashboard = getDoSDashBoardRefreshResponse(); @@ -113,28 +118,48 @@ public void runManifestVaryingDemo() { assertEquals(0, riskLimit .compareTo(new BigDecimal(dashboard.get("audit_info.risk_limit").toString()))); - // 1. Estimate Sample sizes, without the manifest. This should count the CSVs. + // Estimate Sample sizes, without the manifest. This should count the CSVs. Sanity check. Map estimatesWithoutManifests = getSampleSizeEstimates(); - assertEquals(estimatesWithoutManifests.size(), 2); + assertEquals(estimatesWithoutManifests.size(), 6); + + // Upload the manifests. + // County 1 gets a manifest that matches the CSV count + // County 2 gets a manifest with double the CSV count. + // County 3 gets a manifest with inadequate count (should cause an error). + List MANIFESTS = new ArrayList<>(); + MANIFESTS.add(dataPath + "HundredVotes_Manifest.csv"); + MANIFESTS.add(dataPath + "HundredVotes_DoubledManifest.csv"); + MANIFESTS.add(dataPath + "HundredVotes_InsufficientManifest.csv"); + for(int i=1 ; i <= 3 ; i++) { + uploadCounty(i, "ballot-manifest", MANIFESTS.get(i-1), MANIFESTS.get(i-1) + ".sha256sum"); + } // 2. Upload the manifest that matches the CSV count, then get estimates. // All the estimate data should be the same, because EstimateSampleSizes doesn't use manifests. - // TODO do this with doubled manifests too. - String matchingManifest = dataPath + "HundredVotes_Manifest.csv"; - uploadCounty(1, "ballot-manifest", matchingManifest, matchingManifest + ".sha256sum"); - // TODO targetContests doesn't work without manifests (which is good) but also doesn't seem to throw an error (though it should). - targetContests(Map.of(margin2Contest, "COUNTY_WIDE_CONTEST", margin10Contest,"COUNTY_WIDE_CONTEST")); - Map estimatesWithMatchingManifests = getSampleSizeEstimates(); + Map estimatesWithManifests = getSampleSizeEstimates(); dashboard = getDoSDashBoardRefreshResponse(); - assertEquals(estimatesWithMatchingManifests.get(margin2Contest), estimatesWithoutManifests.get(margin2Contest)); - assertEquals(estimatesWithMatchingManifests.get(margin10Contest), estimatesWithoutManifests.get(margin10Contest)); + for(String s : suffixes) { + assertEquals(estimatesWithManifests.get(margin2Contest+s), estimatesWithoutManifests.get(margin2Contest+s)); + assertEquals(estimatesWithManifests.get(margin10Contest+s), estimatesWithoutManifests.get(margin10Contest+s)); + } + + final int margin2Estimate = estimatesWithManifests.get(margin2Contest).estimatedSamples(); + + // Target the margin-2 contests in every county. + // TODO targetContests doesn't work without manifests (which is good) but also doesn't seem to throw an error (though it should). + targetContests(Map.of( + margin2Contest, "COUNTY_WIDE_CONTEST", + margin2Contest + suffixes[1], "COUNTY_WIDE_CONTEST", + margin2Contest + suffixes[2], "COUNTY_WIDE_CONTEST" + )); - // Check that the estimatedSampleSizes inside corla also match the pre-audit estimates. + // 3. Check that for County 1, the estimatedSampleSizes inside corla also match the pre-audit estimates. // They should, because the manifests and CVR files have equal counts. startAuditRound(); dashboard = getDoSDashBoardRefreshResponse(); - int margin2Estimate = dashboard.getInt("county_status.1.estimated_ballots_to_audit"); - int margin10Estimate = dashboard.getInt("county_status.1.estimated_ballots_to_audit"); + assertEquals(dashboard.getInt("county_status.1.estimated_ballots_to_audit"), margin2Estimate); + assertEquals(dashboard.getInt("county_status.2.estimated_ballots_to_audit"), margin2Estimate); + assertEquals(dashboard.getInt("county_status.3.estimated_ballots_to_audit"), margin2Estimate); // 3. Upload the manifest that claims double the CSV count. This should double the sample size // estimates. diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_DoubledManifest.csv.sha256sum b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_DoubledManifest.csv.sha256sum index 734acd10..1f2771be 100644 --- a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_DoubledManifest.csv.sha256sum +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_DoubledManifest.csv.sha256sum @@ -1 +1 @@ -de8fd74f9615c9332cb43683bd835c69d1a992f5a944a7610528b354239171fb +68f2df73c0e2b45de4d3c543b47dc68a72003379011fd346097381824eaccc3b diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_OneAndAHalfManifest.csv b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_OneAndAHalfManifest.csv deleted file mode 100644 index 2cb9f2b7..00000000 --- a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_OneAndAHalfManifest.csv +++ /dev/null @@ -1,2 +0,0 @@ -CountyID,ScannerID,BatchID,NumBallots,StorageLocation -TestCounty,1,1,150,Bin 1 diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_OneAndAHalfManifest.csv.sha256sum b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_OneAndAHalfManifest.csv.sha256sum deleted file mode 100644 index 09ddb9ec..00000000 --- a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_OneAndAHalfManifest.csv.sha256sum +++ /dev/null @@ -1 +0,0 @@ -7cff756072e15d732465d754f8a430e4af18bad24af1cc604f31e455b4d630de diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10MarginsCopy2.csv b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10MarginsCopy2.csv new file mode 100644 index 00000000..cd41010f --- /dev/null +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10MarginsCopy2.csv @@ -0,0 +1,104 @@ +Plurality-Margins2-and-10,5.10.11.24,,,,,,,,,,, +,,,,,,,PluralityMargin2Copy2 (Vote For=1),PluralityMargin2Copy2 (Vote For=1),PluralityMargin2Copy2 (Vote For=1),PluralityMargin10Copy2 (Vote For=2),PluralityMargin10Copy2 (Vote For=2),PluralityMargin10Copy2 (Vote For=2) +,,,,,,,Diego,Eli,Farhad,Gertrude,Ho,Imogen +CvrNumber,TabulatorNum,BatchId,RecordId,ImprintedId,PrecinctPortion,BallotType,,,,,, +1,1,1,1,1-1-1,Precinct 1,Ballot 1 - Type 1,1,0,0,0,1,1 +2,1,1,2,1-1-2,Precinct 1,Ballot 1 - Type 1,1,0,0,0,1,1 +3,1,1,3,1-1-3,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +4,1,1,4,1-1-4,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +5,1,1,5,1-1-5,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +6,1,1,6,1-1-6,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +7,1,1,7,1-1-7,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +8,1,1,8,1-1-8,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +9,1,1,9,1-1-9,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +10,1,1,10,1-1-10,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +11,1,1,11,1-1-11,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +12,1,1,12,1-1-12,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +13,1,1,13,1-1-13,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +14,1,1,14,1-1-14,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +15,1,1,15,1-1-15,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +16,1,1,16,1-1-16,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +17,1,1,17,1-1-17,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +18,1,1,18,1-1-18,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +19,1,1,19,1-1-19,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +20,1,1,20,1-1-20,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +21,1,1,21,1-1-21,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +22,1,1,22,1-1-22,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +23,1,1,23,1-1-23,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +24,1,1,24,1-1-24,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +25,1,1,25,1-1-25,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +26,1,1,26,1-1-26,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +27,1,1,27,1-1-27,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +28,1,1,28,1-1-28,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +29,1,1,29,1-1-29,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +30,1,1,30,1-1-30,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +31,1,1,31,1-1-31,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +32,1,1,32,1-1-32,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +33,1,1,33,1-1-33,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +34,1,1,34,1-1-34,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +35,1,1,35,1-1-35,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +36,1,1,36,1-1-36,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +37,1,1,37,1-1-37,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +38,1,1,38,1-1-38,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +39,1,1,39,1-1-39,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +40,1,1,40,1-1-40,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +41,1,1,41,1-1-41,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +42,1,1,42,1-1-42,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +43,1,1,43,1-1-43,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +44,1,1,44,1-1-44,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +45,1,1,45,1-1-45,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +46,1,1,46,1-1-46,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +47,1,1,47,1-1-47,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +48,1,1,48,1-1-48,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +49,1,1,49,1-1-49,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +50,1,1,50,1-1-50,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +51,1,1,51,1-1-51,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +52,1,1,52,1-1-52,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +53,1,1,53,1-1-53,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +54,1,1,54,1-1-54,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +55,1,1,55,1-1-55,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +56,1,1,56,1-1-56,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +57,1,1,57,1-1-57,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +58,1,1,58,1-1-58,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +59,1,1,59,1-1-59,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +60,1,1,60,1-1-60,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +61,1,1,61,1-1-61,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +62,1,1,62,1-1-62,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +63,1,1,63,1-1-63,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +64,1,1,64,1-1-64,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +65,1,1,65,1-1-65,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +66,1,1,66,1-1-66,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +67,1,1,67,1-1-67,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +68,1,1,68,1-1-68,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +69,1,1,69,1-1-69,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +70,1,1,70,1-1-70,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +71,1,1,71,1-1-71,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +72,1,1,72,1-1-72,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +73,1,1,73,1-1-73,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +74,1,1,74,1-1-74,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +75,1,1,75,1-1-75,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +76,1,1,76,1-1-76,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +77,1,1,77,1-1-77,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +78,1,1,78,1-1-78,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +79,1,1,79,1-1-79,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +80,1,1,80,1-1-80,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +81,1,1,81,1-1-81,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +82,1,1,82,1-1-82,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +83,1,1,83,1-1-83,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +84,1,1,84,1-1-84,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +85,1,1,85,1-1-85,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +86,1,1,86,1-1-86,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +87,1,1,87,1-1-87,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +88,1,1,88,1-1-88,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +89,1,1,89,1-1-89,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +90,1,1,90,1-1-90,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +91,1,1,91,1-1-91,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +92,1,1,92,1-1-92,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +93,1,1,93,1-1-93,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +94,1,1,94,1-1-94,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +95,1,1,95,1-1-95,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +96,1,1,96,1-1-96,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +97,1,1,97,1-1-97,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +98,1,1,98,1-1-98,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +99,1,1,99,1-1-99,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +100,1,1,100,1-1-100,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10MarginsCopy3.csv b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10MarginsCopy3.csv new file mode 100644 index 00000000..481d54a0 --- /dev/null +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10MarginsCopy3.csv @@ -0,0 +1,104 @@ +Plurality-Margins2-and-10,5.10.11.24,,,,,,,,,,, +,,,,,,,PluralityMargin2Copy3 (Vote For=1),PluralityMargin2Copy3 (Vote For=1),PluralityMargin2Copy3 (Vote For=1),PluralityMargin10Copy3 (Vote For=2),PluralityMargin10Copy3 (Vote For=2),PluralityMargin10Copy3 (Vote For=2) +,,,,,,,Diego,Eli,Farhad,Gertrude,Ho,Imogen +CvrNumber,TabulatorNum,BatchId,RecordId,ImprintedId,PrecinctPortion,BallotType,,,,,, +1,1,1,1,1-1-1,Precinct 1,Ballot 1 - Type 1,1,0,0,0,1,1 +2,1,1,2,1-1-2,Precinct 1,Ballot 1 - Type 1,1,0,0,0,1,1 +3,1,1,3,1-1-3,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +4,1,1,4,1-1-4,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +5,1,1,5,1-1-5,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +6,1,1,6,1-1-6,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +7,1,1,7,1-1-7,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +8,1,1,8,1-1-8,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +9,1,1,9,1-1-9,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +10,1,1,10,1-1-10,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +11,1,1,11,1-1-11,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +12,1,1,12,1-1-12,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +13,1,1,13,1-1-13,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +14,1,1,14,1-1-14,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +15,1,1,15,1-1-15,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +16,1,1,16,1-1-16,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +17,1,1,17,1-1-17,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +18,1,1,18,1-1-18,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +19,1,1,19,1-1-19,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +20,1,1,20,1-1-20,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +21,1,1,21,1-1-21,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +22,1,1,22,1-1-22,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +23,1,1,23,1-1-23,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +24,1,1,24,1-1-24,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +25,1,1,25,1-1-25,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +26,1,1,26,1-1-26,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +27,1,1,27,1-1-27,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +28,1,1,28,1-1-28,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +29,1,1,29,1-1-29,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +30,1,1,30,1-1-30,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +31,1,1,31,1-1-31,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +32,1,1,32,1-1-32,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +33,1,1,33,1-1-33,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +34,1,1,34,1-1-34,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +35,1,1,35,1-1-35,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +36,1,1,36,1-1-36,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +37,1,1,37,1-1-37,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +38,1,1,38,1-1-38,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +39,1,1,39,1-1-39,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +40,1,1,40,1-1-40,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +41,1,1,41,1-1-41,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +42,1,1,42,1-1-42,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +43,1,1,43,1-1-43,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +44,1,1,44,1-1-44,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +45,1,1,45,1-1-45,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +46,1,1,46,1-1-46,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +47,1,1,47,1-1-47,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +48,1,1,48,1-1-48,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +49,1,1,49,1-1-49,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +50,1,1,50,1-1-50,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +51,1,1,51,1-1-51,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +52,1,1,52,1-1-52,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +53,1,1,53,1-1-53,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +54,1,1,54,1-1-54,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +55,1,1,55,1-1-55,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +56,1,1,56,1-1-56,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +57,1,1,57,1-1-57,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +58,1,1,58,1-1-58,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +59,1,1,59,1-1-59,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +60,1,1,60,1-1-60,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +61,1,1,61,1-1-61,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +62,1,1,62,1-1-62,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +63,1,1,63,1-1-63,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +64,1,1,64,1-1-64,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +65,1,1,65,1-1-65,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +66,1,1,66,1-1-66,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +67,1,1,67,1-1-67,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +68,1,1,68,1-1-68,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +69,1,1,69,1-1-69,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +70,1,1,70,1-1-70,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +71,1,1,71,1-1-71,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +72,1,1,72,1-1-72,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +73,1,1,73,1-1-73,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +74,1,1,74,1-1-74,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +75,1,1,75,1-1-75,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +76,1,1,76,1-1-76,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +77,1,1,77,1-1-77,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +78,1,1,78,1-1-78,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +79,1,1,79,1-1-79,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +80,1,1,80,1-1-80,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +81,1,1,81,1-1-81,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +82,1,1,82,1-1-82,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +83,1,1,83,1-1-83,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +84,1,1,84,1-1-84,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +85,1,1,85,1-1-85,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +86,1,1,86,1-1-86,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +87,1,1,87,1-1-87,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +88,1,1,88,1-1-88,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +89,1,1,89,1-1-89,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +90,1,1,90,1-1-90,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +91,1,1,91,1-1-91,Precinct 1,Ballot 1 - Type 1,0,0,0,0,1,1 +92,1,1,92,1-1-92,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +93,1,1,93,1-1-93,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,1 +94,1,1,94,1-1-94,Precinct 1,Ballot 1 - Type 1,0,0,1,0,1,0 +95,1,1,95,1-1-95,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +96,1,1,96,1-1-96,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +97,1,1,97,1-1-97,Precinct 1,Ballot 1 - Type 1,1,0,0,1,0,0 +98,1,1,98,1-1-98,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +99,1,1,99,1-1-99,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 +100,1,1,100,1-1-100,Precinct 1,Ballot 1 - Type 1,0,1,0,1,0,0 diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality_Only_Test_Canonical_List.csv b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality_Only_Test_Canonical_List.csv index 08137201..41edb524 100644 --- a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality_Only_Test_Canonical_List.csv +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality_Only_Test_Canonical_List.csv @@ -1,3 +1,7 @@ CountyName,ContestName,ContestChoices Adams,PluralityMargin2,"Diego, Eli, Farhad" Adams,PluralityMargin10,"Gertrude, Ho, Imogen" +Alamosa,PluralityMargin2Copy2,"Diego, Eli, Farhad" +Alamosa,PluralityMargin10Copy2,"Gertrude, Ho, Imogen" +Arapahoe,PluralityMargin2Copy3,"Diego, Eli, Farhad" +Arapahoe,PluralityMargin10Copy3,"Gertrude, Ho, Imogen" From 1b6bdcc7b4e03b41e280aba481fe365ffd36752d Mon Sep 17 00:00:00 2001 From: vteague Date: Sat, 9 Nov 2024 18:04:27 +1100 Subject: [PATCH 27/40] Tests for first 3 cases done. --- .../EstimateSampleSizesVaryingManifests.java | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java index 969867e4..d8b6f19e 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java @@ -153,25 +153,27 @@ public void runManifestVaryingDemo() { margin2Contest + suffixes[2], "COUNTY_WIDE_CONTEST" )); - // 3. Check that for County 1, the estimatedSampleSizes inside corla also match the pre-audit estimates. - // They should, because the manifests and CVR files have equal counts. startAuditRound(); dashboard = getDoSDashBoardRefreshResponse(); - assertEquals(dashboard.getInt("county_status.1.estimated_ballots_to_audit"), margin2Estimate); - assertEquals(dashboard.getInt("county_status.2.estimated_ballots_to_audit"), margin2Estimate); - assertEquals(dashboard.getInt("county_status.3.estimated_ballots_to_audit"), margin2Estimate); - - // 3. Upload the manifest that claims double the CSV count. This should double the sample size - // estimates. - String doubledManifest = dataPath + "HundredVotes_DoubledManifest.csv"; - uploadCounty(1, "ballot-manifest", doubledManifest, doubledManifest + ".sha256sum"); - Map estimatesWithDoubledManifests = getSampleSizeEstimates(); + + // For County 1, the estimated ballots to audit inside corla should also match the pre-audit estimates, + // because the manifests and CVR files have equal counts. + final int county1Estimate = dashboard.getInt("county_status.1.estimated_ballots_to_audit"); + assertEquals(county1Estimate, margin2Estimate); + + // County 2 should have a much larger estimated ballots to audit, because of manifest phantoms. + final int county2Estimate = dashboard.getInt("county_status.2.estimated_ballots_to_audit"); + assertTrue(county2Estimate > margin2Estimate); + + // Run Sample Size estimation again; check that it doesn't change any of the colorado-rla estimates. + getSampleSizeEstimates(); dashboard = getDoSDashBoardRefreshResponse(); - assertEquals(estimatesWithDoubledManifests.size(), 2); - assertEquals(estimatesWithoutManifests.get(margin2Contest).estimatedSamples(), - estimatesWithDoubledManifests.get(margin2Contest).estimatedSamples()/2); - assertEquals(estimatesWithoutManifests.get(margin10Contest).estimatedSamples(), - estimatesWithDoubledManifests.get(margin10Contest).estimatedSamples()/2); + assertEquals(dashboard.getInt("county_status.1.estimated_ballots_to_audit"), county1Estimate); + assertEquals(dashboard.getInt("county_status.2.estimated_ballots_to_audit"), county2Estimate); + + // County 3 should have an error, because the manifest has fewer ballots than the CVR. + // FIXME - think about what should happen when the manifest is smaller than the CSV list. + int test = dashboard.getInt("county_status.3.estimated_ballots_to_audit"); } } From 58cdb3f8ec7d9c77ff3beb092966a45345a82b9e Mon Sep 17 00:00:00 2001 From: vteague Date: Sun, 10 Nov 2024 18:44:27 +1100 Subject: [PATCH 28/40] Test for successful file uploads. --- .../EstimateSampleSizesVaryingManifests.java | 13 +-- .../corla/workflows/Workflow.java | 82 ++++++++++++++----- 2 files changed, 67 insertions(+), 28 deletions(-) diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java index d8b6f19e..45f85d64 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java @@ -38,6 +38,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Set; /** * A demonstration workflow that tests sample size estimation with and without manifests, comparing @@ -90,7 +91,7 @@ public static void beforeAll() { * This "test" uploads CVRs and ballot manifests. */ @Test(enabled = true) - public void runManifestVaryingDemo() { + public void runManifestVaryingDemo() throws InterruptedException { final String margin2Contest = "PluralityMargin2"; final String margin10Contest = "PluralityMargin10"; @@ -104,7 +105,8 @@ public void runManifestVaryingDemo() { CVRFile + suffixes[i-1] + ".csv.sha256sum"); } - //TODO figure out how to wait. + assertTrue(uploadSuccessfulWithin(5, Set.of(1,2,3), "cvr_export_file")); + assertFalse(uploadSuccessfulWithin(5, Set.of(1,2,3), "ballot_manifest_file")); // Get the DoSDashboard refresh response; sanity check. JsonPath dashboard = getDoSDashBoardRefreshResponse(); @@ -137,7 +139,6 @@ public void runManifestVaryingDemo() { // 2. Upload the manifest that matches the CSV count, then get estimates. // All the estimate data should be the same, because EstimateSampleSizes doesn't use manifests. Map estimatesWithManifests = getSampleSizeEstimates(); - dashboard = getDoSDashBoardRefreshResponse(); for(String s : suffixes) { assertEquals(estimatesWithManifests.get(margin2Contest+s), estimatesWithoutManifests.get(margin2Contest+s)); assertEquals(estimatesWithManifests.get(margin10Contest+s), estimatesWithoutManifests.get(margin10Contest+s)); @@ -146,7 +147,6 @@ public void runManifestVaryingDemo() { final int margin2Estimate = estimatesWithManifests.get(margin2Contest).estimatedSamples(); // Target the margin-2 contests in every county. - // TODO targetContests doesn't work without manifests (which is good) but also doesn't seem to throw an error (though it should). targetContests(Map.of( margin2Contest, "COUNTY_WIDE_CONTEST", margin2Contest + suffixes[1], "COUNTY_WIDE_CONTEST", @@ -172,8 +172,9 @@ public void runManifestVaryingDemo() { assertEquals(dashboard.getInt("county_status.2.estimated_ballots_to_audit"), county2Estimate); // County 3 should have an error, because the manifest has fewer ballots than the CVR. - // FIXME - think about what should happen when the manifest is smaller than the CSV list. - int test = dashboard.getInt("county_status.3.estimated_ballots_to_audit"); + // TODO - Verify how colorado-rla deals with the case where the manifest has fewer votes than the + // CVR export - see ... + // int test = dashboard.getInt("county_status.3.estimated_ballots_to_audit"); } } diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java index ae35d464..88e70624 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java @@ -43,6 +43,7 @@ import org.apache.log4j.Logger; import org.apache.http.HttpStatus; import org.testng.annotations.BeforeClass; +import us.freeandfair.corla.model.UploadedFile; import wiremock.net.minidev.json.JSONArray; import wiremock.net.minidev.json.JSONObject; @@ -83,11 +84,11 @@ public void setup() { * CLI, but only as a file, so we need to make the file and then tell main to read it. * @param testFileName the name of the test file - must be different for each test. */ - protected static void runMain(String testFileName) { + protected static void runMain(final String testFileName) { final String propertiesFile = tempConfigPath +testFileName+"-test.properties"; try { FileOutputStream os = new FileOutputStream(propertiesFile); - StringWriter sw = new StringWriter(); + final StringWriter sw = new StringWriter(); config.store(sw, "Ephemeral database config for Demo1"); os.write(sw.toString().getBytes()); os.close(); @@ -98,7 +99,7 @@ protected static void runMain(String testFileName) { } protected JSONObject createBody(final Map data) { - JSONObject body = new JSONObject(); + final JSONObject body = new JSONObject(); body.putAll(data); return body; } @@ -135,7 +136,7 @@ protected void authenticate(final SessionFilter filter, final String user, final * @param user Username to unauthenticate. */ protected void logout(final SessionFilter filter, final String user) { - JSONObject requestParams = createBody(Map.of("username", user)); + final JSONObject requestParams = createBody(Map.of("username", user)); given() .filter(filter) @@ -156,7 +157,7 @@ protected void uploadCounty(final int number, final String fileType, final String prefix = "[uploadCounty]"; final String user = "countyadmin" + number; - SessionFilter filter = doLogin(user); + final SessionFilter filter = doLogin(user); // GET the county dashboard. This is just to test that the login worked. given().filter(filter).get("/county-dashboard"); @@ -223,7 +224,7 @@ protected Map getSampleSizeEstimates() // Login as state admin. final SessionFilter filter = doLogin("stateadmin1"); - String data = given() + final String data = given() .filter(filter) .get("/estimate-sample-sizes") .then() @@ -233,19 +234,19 @@ protected Map getSampleSizeEstimates() .body() .asString(); - Map estimates = new HashMap<>(); - var lines = data.split("\n"); + final Map estimates = new HashMap<>(); + final String[] lines = data.split("\n"); // Skip the first line (which has headers) for(int i = 1 ; i < lines.length ; i++) { - var line = lines[i].split(","); + final String[] line = lines[i].split(","); if(line.length < 7) { final String msg = prefix + " Invalid sample size estimate data"; LOGGER.error(msg); throw new RuntimeException(msg); } - EstimateSampleSizes.EstimateData estimate = new EstimateSampleSizes.EstimateData( + final EstimateSampleSizes.EstimateData estimate = new EstimateSampleSizes.EstimateData( line[0], line[1], line[2], @@ -266,17 +267,17 @@ protected Map getSampleSizeEstimates() * @param canonicalListFile the path to the canonical list csv file. * @param riskLimit the risk limit. */ - protected void updateAuditInfo(String canonicalListFile, BigDecimal riskLimit) { + protected void updateAuditInfo(final String canonicalListFile, final BigDecimal riskLimit) { // Login as state admin. final SessionFilter filter = doLogin("stateadmin1"); - JSONObject requestParams = new JSONObject(); + final JSONObject requestParams = new JSONObject(); requestParams.put("risk_limit", riskLimit); requestParams.put("election_date","2024-09-15T05:42:17.796Z"); requestParams.put("election_type","general"); requestParams.put("public_meeting_date","2024-09-22T05:42:22.037Z"); - JSONObject canonicalListContents = new JSONObject(); + final JSONObject canonicalListContents = new JSONObject(); canonicalListContents.put("contents",readFromFile(canonicalListFile)); requestParams.put("upload_file", List.of(canonicalListContents)); @@ -290,13 +291,50 @@ protected void updateAuditInfo(String canonicalListFile, BigDecimal riskLimit) { .statusCode(HttpStatus.SC_OK); } + /** + * Checks that all the given counties have completed their CVR upload within the timeout. + * @param counties the counties to wait for, by county ID. + * @param timeAllowedSeconds the maximum time, in seconds, to wait for. + * @return true if all uploads were successful within timeoutSeconds, false if any failed or the timeout was reached. + * Timing is imprecise and assumes all the calls take no time. + */ + protected boolean uploadSuccessfulWithin(int timeAllowedSeconds, final Set counties, final String fileType) throws InterruptedException { + + JsonPath dashboard = getDoSDashBoardRefreshResponse(); + while (timeAllowedSeconds-- > 0) { + List succeededCounties = new ArrayList<>(); + for (int c : counties) { + final String status = dashboard.getString("county_status." + c + "." + fileType + ".status"); + // Upload failed (e.g. a hash mismatch). + if (status != null && status.equals(UploadedFile.FileStatus.FAILED.toString())) { + return false; + // This county succeeded. + } else if (status != null && status.equals(UploadedFile.FileStatus.IMPORTED.toString())) { + succeededCounties.add(c); + } + } + if (succeededCounties.size() == counties.size()) { + return true; + } else { + Thread.sleep(1000); + dashboard = getDoSDashBoardRefreshResponse(); + } + } + + // Timeout. + return false; + + + } + /** * Generate assertions (for IRV contests) * TODO At the moment this expects the raire-service to be running, which is a problem because it will be reading the + * See ... * wrong database. * Set it up so that we run raire-service inside the Docker container and tell main where to find it. */ - protected void generateAssertions(double timeLimitSeconds) { + protected void generateAssertions(final double timeLimitSeconds) { // Login as state admin. final SessionFilter filter = doLogin("stateadmin1"); @@ -323,7 +361,7 @@ protected void startAuditRound() { /** * Select contests to target, by name. */ - protected void targetContests(Map targetedContestsWithReasons) { + protected void targetContests(final Map targetedContestsWithReasons) { // Login as state admin. final SessionFilter filter = doLogin("stateadmin1"); @@ -341,14 +379,14 @@ protected void targetContests(Map targetedContestsWithReasons) { .jsonPath(); // The contests and reasons to be requested. - JSONArray contestSelections = new JSONArray(); + final JSONArray contestSelections = new JSONArray(); // Find the IDs of the ones we want to target. for(int i=0 ; i < contests.getList("").size() ; i++) { final String contestName = contests.getString("[" + i + "].name"); // If this contest's name is one of the targeted ones... - String reason = targetedContestsWithReasons.get(contestName); + final String reason = targetedContestsWithReasons.get(contestName); if(reason != null) { // add it to the selections. final JSONObject contestSelection = new JSONObject(); @@ -376,11 +414,11 @@ protected void targetContests(Map targetedContestsWithReasons) { /** * Set the seed for the audit. */ - protected void setSeed(String seed) { + protected void setSeed(final String seed) { // Login as state admin. final SessionFilter filter = doLogin("stateadmin1"); - JSONObject requestParams = new JSONObject(); + final JSONObject requestParams = new JSONObject(); requestParams.put("seed", seed); given() @@ -402,15 +440,15 @@ protected void setSeed(String seed) { private String readFromFile(final String fileName) { final String prefix = "[readFromFile]"; try { - Path path = Paths.get(fileName); + final Path path = Paths.get(fileName); return String.join("\n",Files.readAllLines(path)); - } catch (IOException ex) { + } catch (final IOException ex) { LOGGER.error(prefix + ex.getMessage()); return ""; } } - private SessionFilter doLogin(String username) { + private SessionFilter doLogin(final String username) { final SessionFilter filter = new SessionFilter(); authenticate(filter, username,"",1); authenticate(filter, username,"s d f",2); From 51cab905b874564993264c22ac56abf0928b6664 Mon Sep 17 00:00:00 2001 From: vteague Date: Sun, 10 Nov 2024 20:07:33 +1100 Subject: [PATCH 29/40] Added test file sha256sums. --- .../au/org/democracydevelopers/corla/workflows/Demo1.java | 4 ++-- .../HundredVotes_InsufficientManifest.csv.sha256sum | 1 + .../Plurality100votes2And10MarginsCopy2.csv.sha256sum | 1 + .../Plurality100votes2And10MarginsCopy3.csv.sha256sum | 1 + 4 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_InsufficientManifest.csv.sha256sum create mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10MarginsCopy2.csv.sha256sum create mode 100644 server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10MarginsCopy3.csv.sha256sum diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java index 254c5587..dfe28456 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java @@ -43,7 +43,7 @@ * A demonstration workflow that uploads CVRs and ballot manifests for all 64 counties. * This assumes that main is running. */ -@Test(enabled=true) +@Test(enabled=false) public class Demo1 extends Workflow { /** @@ -85,7 +85,7 @@ public static void beforeAll() { * - demo1_loadCVRs, demo1_loadManifests, * - Boulder_loadCVRs, Boulder_loadManifest. */ - @Test(enabled = true) + @Test(enabled = false) public void runDemo1(){ diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_InsufficientManifest.csv.sha256sum b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_InsufficientManifest.csv.sha256sum new file mode 100644 index 00000000..734acd10 --- /dev/null +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/HundredVotes_InsufficientManifest.csv.sha256sum @@ -0,0 +1 @@ +de8fd74f9615c9332cb43683bd835c69d1a992f5a944a7610528b354239171fb diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10MarginsCopy2.csv.sha256sum b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10MarginsCopy2.csv.sha256sum new file mode 100644 index 00000000..13219e8e --- /dev/null +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10MarginsCopy2.csv.sha256sum @@ -0,0 +1 @@ +eac9c1b4fb9a0733c767233760e3b2cd440b4aa030e2a4ef6fb685fec1c82b96 diff --git a/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10MarginsCopy3.csv.sha256sum b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10MarginsCopy3.csv.sha256sum new file mode 100644 index 00000000..56815562 --- /dev/null +++ b/server/eclipse-project/src/test/resources/CSVs/PluralityOnly/Plurality100votes2And10MarginsCopy3.csv.sha256sum @@ -0,0 +1 @@ +cbbf58dbe748237c0340e50645cdd1af02e4845e4029bd0cffd9197ba462eda2 From 147b07344a5f5042e8595f8a9e2587dfc77f52cd Mon Sep 17 00:00:00 2001 From: vteague Date: Sun, 10 Nov 2024 20:51:00 +1100 Subject: [PATCH 30/40] Get Demo1 to wait for CVR and manifest uploads. --- .../corla/workflows/Demo1.java | 16 +++++++++------- .../corla/workflows/Workflow.java | 9 +++++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java index dfe28456..27f1cfe4 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java @@ -22,12 +22,11 @@ package au.org.democracydevelopers.corla.workflows; import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; import au.org.democracydevelopers.corla.endpoint.EstimateSampleSizes; import io.restassured.path.json.JsonPath; +import org.apache.commons.lang.math.Range; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.testcontainers.containers.PostgreSQLContainer; @@ -43,7 +42,7 @@ * A demonstration workflow that uploads CVRs and ballot manifests for all 64 counties. * This assumes that main is running. */ -@Test(enabled=false) +@Test(enabled=true) public class Demo1 extends Workflow { /** @@ -85,8 +84,8 @@ public static void beforeAll() { * - demo1_loadCVRs, demo1_loadManifests, * - Boulder_loadCVRs, Boulder_loadManifest. */ - @Test(enabled = false) - public void runDemo1(){ + @Test(enabled = true) + public void runDemo1() throws InterruptedException { List CVRS = new ArrayList<>(); @@ -121,11 +120,14 @@ public void runDemo1(){ // 2. Then upload all the CVRs. The order is important because it's an error to try to import a manifest while the CVRs // are being read. - // TODO Note this takes a while - we'll need to add a wait for anything that assumes all the CVRs are loaded. for(int i = 1; i <= numCounties ; ++i){ uploadCounty(i, "cvr-export", CVRS.get(i-1), CVRS.get(i-1) + ".sha256sum"); } + // Wait while the CVRs (and manifests) are uploaded. This can take a while, especially Boulder(7). + assertTrue(uploadSuccessfulWithin(600, allCounties, "cvr_export_file")); + assertTrue(uploadSuccessfulWithin(20, allCounties, "ballot_manifest_file")); + // Get the DoSDashboard refresh response; sanity check for initial state. JsonPath dashboard = getDoSDashBoardRefreshResponse(); assertEquals(dashboard.get("asm_state"), DOS_INITIAL_STATE.toString()); diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java index 88e70624..1df01ef4 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java @@ -38,6 +38,8 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; @@ -58,6 +60,13 @@ public class Workflow extends TestClassWithDatabase { */ protected static final int numCounties = 64; + /** + * Set of all CO counties, by number. + */ + protected static final Set allCounties + = IntStream.rangeClosed(1,numCounties).boxed().collect(Collectors.toSet()); + + /** * Default PRNG seed. */ From 07065652dc05bbe485d776bf58c925496e8882ef Mon Sep 17 00:00:00 2001 From: vteague Date: Sun, 10 Nov 2024 21:44:33 +1100 Subject: [PATCH 31/40] Code cleanup. --- .../corla/workflows/Demo1.java | 37 ++++++------ .../EstimateSampleSizesVaryingManifests.java | 31 ++++++---- .../corla/workflows/Workflow.java | 59 ++++++++++++++----- 3 files changed, 81 insertions(+), 46 deletions(-) diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java index 27f1cfe4..d0182a84 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java @@ -25,8 +25,8 @@ import java.util.*; import au.org.democracydevelopers.corla.endpoint.EstimateSampleSizes; +import au.org.democracydevelopers.corla.util.testUtils; import io.restassured.path.json.JsonPath; -import org.apache.commons.lang.math.Range; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.testcontainers.containers.PostgreSQLContainer; @@ -40,7 +40,8 @@ /** * A demonstration workflow that uploads CVRs and ballot manifests for all 64 counties. - * This assumes that main is running. + * At the moment, this seems to run fine if run alone, but not to run in parallel with + * EstimateSamplesVaryingManifests, or in * github actions. Hence currently disabled. */ @Test(enabled=true) public class Demo1 extends Workflow { @@ -86,7 +87,7 @@ public static void beforeAll() { */ @Test(enabled = true) public void runDemo1() throws InterruptedException { - + testUtils.log(LOGGER, "Demo1"); List CVRS = new ArrayList<>(); @@ -115,37 +116,37 @@ public void runDemo1() throws InterruptedException { // 1. First upload all the manifests for(int i = 1 ; i <= numCounties ; ++i){ - uploadCounty(i, "ballot-manifest", MANIFESTS.get(i-1), MANIFESTS.get(i-1) + ".sha256sum"); + uploadCounty(i, MANIFEST_FILETYPE, MANIFESTS.get(i-1), MANIFESTS.get(i-1) + ".sha256sum"); } // 2. Then upload all the CVRs. The order is important because it's an error to try to import a manifest while the CVRs // are being read. for(int i = 1; i <= numCounties ; ++i){ - uploadCounty(i, "cvr-export", CVRS.get(i-1), CVRS.get(i-1) + ".sha256sum"); + uploadCounty(i, CVR_FILETYPE, CVRS.get(i-1), CVRS.get(i-1) + ".sha256sum"); } // Wait while the CVRs (and manifests) are uploaded. This can take a while, especially Boulder(7). - assertTrue(uploadSuccessfulWithin(600, allCounties, "cvr_export_file")); - assertTrue(uploadSuccessfulWithin(20, allCounties, "ballot_manifest_file")); + assertTrue(uploadSuccessfulWithin(600, allCounties, CVR_JSON)); + assertTrue(uploadSuccessfulWithin(20, allCounties, MANIFEST_JSON)); // Get the DoSDashboard refresh response; sanity check for initial state. JsonPath dashboard = getDoSDashBoardRefreshResponse(); - assertEquals(dashboard.get("asm_state"), DOS_INITIAL_STATE.toString()); - assertEquals(dashboard.getMap("audit_info.canonicalChoices").toString(), "{}"); - assertNull(dashboard.get("audit_info.risk_limit")); - assertNull(dashboard.get("audit_info.seed")); + assertEquals(dashboard.get(ASM_STATE), DOS_INITIAL_STATE.toString()); + assertEquals(dashboard.getMap(AUDIT_INFO + "." + CANONICAL_CHOICES).toString(), "{}"); + assertNull(dashboard.get(AUDIT_INFO + "." + RISK_LIMIT_JSON)); + assertNull(dashboard.get(AUDIT_INFO + "." + SEED)); // 3. Set the audit info, including the canonical list and the risk limit; check for update. final BigDecimal riskLimit = BigDecimal.valueOf(0.03); updateAuditInfo(dataPath + "Demo1/" + "demo1-canonical-list.csv", riskLimit); dashboard = getDoSDashBoardRefreshResponse(); assertEquals(0, riskLimit - .compareTo(new BigDecimal(dashboard.get("audit_info.risk_limit").toString()))); + .compareTo(new BigDecimal(dashboard.get(AUDIT_INFO + "." + RISK_LIMIT_JSON).toString()))); // There should be canonical contests for each county. - assertEquals(numCounties, dashboard.getMap("audit_info.canonicalContests").values().size()); + assertEquals(numCounties, dashboard.getMap(AUDIT_INFO + "." + CANONICAL_CONTESTS).values().size()); // Check that the seed is still null. - assertNull(dashboard.get("audit_info.seed")); - assertEquals(dashboard.get("asm_state"), PARTIAL_AUDIT_INFO_SET.toString()); + assertNull(dashboard.get(AUDIT_INFO + "." + SEED)); + assertEquals(dashboard.get(ASM_STATE), PARTIAL_AUDIT_INFO_SET.toString()); // 4. Generate assertions; sanity check // TODO this is commented out for now while we figure out how to run the raire-service in the Docker container. @@ -163,14 +164,14 @@ public void runDemo1() throws InterruptedException { // This should be complete audit info. dashboard = getDoSDashBoardRefreshResponse(); - assertEquals(dashboard.get("audit_info.seed"), defaultSeed); - assertEquals(dashboard.get("asm_state"), COMPLETE_AUDIT_INFO_SET.toString()); + assertEquals(dashboard.get(AUDIT_INFO + "." + SEED), defaultSeed); + assertEquals(dashboard.get(ASM_STATE), COMPLETE_AUDIT_INFO_SET.toString()); // 7. Estimate sample sizes; sanity check. Map sampleSizes = getSampleSizeEstimates(); assertFalse(sampleSizes.isEmpty()); - // TODO get assertions, sanity check. + // TODO get assertions, sanity check, but first get the raire-service working (see above). } } diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java index 45f85d64..e7c0aa4b 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java @@ -22,6 +22,7 @@ package au.org.democracydevelopers.corla.workflows; import au.org.democracydevelopers.corla.endpoint.EstimateSampleSizes; +import au.org.democracydevelopers.corla.util.testUtils; import io.restassured.path.json.JsonPath; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; @@ -33,6 +34,7 @@ import static org.testng.Assert.*; import static us.freeandfair.corla.asm.ASMState.DoSDashboardState.DOS_INITIAL_STATE; +import static us.freeandfair.corla.model.AuditReason.COUNTY_WIDE_CONTEST; import java.math.BigDecimal; import java.util.ArrayList; @@ -50,6 +52,8 @@ * the manifest, and (if present) the number of ballots it states. * Note that it does not test for correct sample sizes, only for correct _changes_ to the sample * sizes as a consequence of changes to the manifest. + * At the moment, this seems to run fine if run alone, but not to run in parallel with Demo1 or in + * github actions. Hence currently disabled. */ @Test(enabled=true) public class EstimateSampleSizesVaryingManifests extends Workflow { @@ -92,6 +96,7 @@ public static void beforeAll() { */ @Test(enabled = true) public void runManifestVaryingDemo() throws InterruptedException { + testUtils.log(LOGGER, "runManifestVaryingDemo"); final String margin2Contest = "PluralityMargin2"; final String margin10Contest = "PluralityMargin10"; @@ -101,16 +106,16 @@ public void runManifestVaryingDemo() throws InterruptedException { final String[] suffixes = {"", "Copy2", "Copy3"}; for(int i = 1 ; i <= 3 ; i++) { - uploadCounty(i, "cvr-export", CVRFile + suffixes[i-1] + ".csv", + uploadCounty(i, CVR_FILETYPE, CVRFile + suffixes[i-1] + ".csv", CVRFile + suffixes[i-1] + ".csv.sha256sum"); } - assertTrue(uploadSuccessfulWithin(5, Set.of(1,2,3), "cvr_export_file")); - assertFalse(uploadSuccessfulWithin(5, Set.of(1,2,3), "ballot_manifest_file")); + assertTrue(uploadSuccessfulWithin(5, Set.of(1,2,3), CVR_JSON)); + assertFalse(uploadSuccessfulWithin(5, Set.of(1,2,3), MANIFEST_JSON)); // Get the DoSDashboard refresh response; sanity check. JsonPath dashboard = getDoSDashBoardRefreshResponse(); - assertEquals(dashboard.get("asm_state"), DOS_INITIAL_STATE.toString()); + assertEquals(dashboard.get(ASM_STATE), DOS_INITIAL_STATE.toString()); // Set the audit info, including the canonical list and the (stupidly large) risk limit; sanity check. final BigDecimal riskLimit = BigDecimal.valueOf(0.5); @@ -118,7 +123,7 @@ public void runManifestVaryingDemo() throws InterruptedException { setSeed(defaultSeed); dashboard = getDoSDashBoardRefreshResponse(); assertEquals(0, riskLimit - .compareTo(new BigDecimal(dashboard.get("audit_info.risk_limit").toString()))); + .compareTo(new BigDecimal(dashboard.get(AUDIT_INFO + "." + RISK_LIMIT_JSON).toString()))); // Estimate Sample sizes, without the manifest. This should count the CSVs. Sanity check. Map estimatesWithoutManifests = getSampleSizeEstimates(); @@ -133,7 +138,7 @@ public void runManifestVaryingDemo() throws InterruptedException { MANIFESTS.add(dataPath + "HundredVotes_DoubledManifest.csv"); MANIFESTS.add(dataPath + "HundredVotes_InsufficientManifest.csv"); for(int i=1 ; i <= 3 ; i++) { - uploadCounty(i, "ballot-manifest", MANIFESTS.get(i-1), MANIFESTS.get(i-1) + ".sha256sum"); + uploadCounty(i, MANIFEST_FILETYPE, MANIFESTS.get(i-1), MANIFESTS.get(i-1) + ".sha256sum"); } // 2. Upload the manifest that matches the CSV count, then get estimates. @@ -148,9 +153,9 @@ public void runManifestVaryingDemo() throws InterruptedException { // Target the margin-2 contests in every county. targetContests(Map.of( - margin2Contest, "COUNTY_WIDE_CONTEST", - margin2Contest + suffixes[1], "COUNTY_WIDE_CONTEST", - margin2Contest + suffixes[2], "COUNTY_WIDE_CONTEST" + margin2Contest, COUNTY_WIDE_CONTEST.toString(), + margin2Contest + suffixes[1], COUNTY_WIDE_CONTEST.toString(), + margin2Contest + suffixes[2], COUNTY_WIDE_CONTEST.toString() )); startAuditRound(); @@ -158,18 +163,18 @@ public void runManifestVaryingDemo() throws InterruptedException { // For County 1, the estimated ballots to audit inside corla should also match the pre-audit estimates, // because the manifests and CVR files have equal counts. - final int county1Estimate = dashboard.getInt("county_status.1.estimated_ballots_to_audit"); + final int county1Estimate = dashboard.getInt(COUNTY_STATUS + ".1." + ESTIMATED_BALLOTS); assertEquals(county1Estimate, margin2Estimate); // County 2 should have a much larger estimated ballots to audit, because of manifest phantoms. - final int county2Estimate = dashboard.getInt("county_status.2.estimated_ballots_to_audit"); + final int county2Estimate = dashboard.getInt(COUNTY_STATUS + ".2." + ESTIMATED_BALLOTS); assertTrue(county2Estimate > margin2Estimate); // Run Sample Size estimation again; check that it doesn't change any of the colorado-rla estimates. getSampleSizeEstimates(); dashboard = getDoSDashBoardRefreshResponse(); - assertEquals(dashboard.getInt("county_status.1.estimated_ballots_to_audit"), county1Estimate); - assertEquals(dashboard.getInt("county_status.2.estimated_ballots_to_audit"), county2Estimate); + assertEquals(dashboard.getInt(COUNTY_STATUS + ".1." + ESTIMATED_BALLOTS), county1Estimate); + assertEquals(dashboard.getInt(COUNTY_STATUS + ".2." + ESTIMATED_BALLOTS), county2Estimate); // County 3 should have an error, because the manifest has fewer ballots than the CVR. // TODO - Verify how colorado-rla deals with the case where the manifest has fewer votes than the diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java index 1df01ef4..fdff79c3 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java @@ -24,6 +24,10 @@ import static io.restassured.RestAssured.given; import static org.testng.Assert.assertEquals; import static us.freeandfair.corla.Main.main; +import static us.freeandfair.corla.auth.AuthenticationInterface.USERNAME; +import static us.freeandfair.corla.auth.AuthenticationStage.SECOND_FACTOR_AUTHENTICATED; +import static us.freeandfair.corla.auth.AuthenticationStage.TRADITIONALLY_AUTHENTICATED; +import static us.freeandfair.corla.model.AuditType.COMPARISON; import au.org.democracydevelopers.corla.endpoint.EstimateSampleSizes; import au.org.democracydevelopers.corla.util.TestClassWithDatabase; @@ -66,7 +70,6 @@ public class Workflow extends TestClassWithDatabase { protected static final Set allCounties = IntStream.rangeClosed(1,numCounties).boxed().collect(Collectors.toSet()); - /** * Default PRNG seed. */ @@ -82,6 +85,31 @@ public class Workflow extends TestClassWithDatabase { */ private static final String tempConfigPath = "src/test/workflows/temp/"; + /** + * Strings for colorado-rla JSON structures. + */ + protected static final String CVR_FILETYPE = "cvr-export"; + protected static final String MANIFEST_FILETYPE = "ballot-manifest"; + protected static final String CVR_JSON = "cvr_export_file"; + protected static final String MANIFEST_JSON = "ballot_manifest_file"; + protected static final String ASM_STATE = "asm_state"; + protected static final String AUDIT_INFO = "audit_info"; + protected static final String RISK_LIMIT_JSON = "risk_limit"; + protected static final String SEED = "seed"; + protected static final String CANONICAL_CONTESTS = "canonicalContests"; + protected static final String CANONICAL_CHOICES = "canonicalChoices"; + protected static final String COUNTY_STATUS = "county_status"; + protected static final String ESTIMATED_BALLOTS = "estimated_ballots_to_audit"; + protected static final String ELECTION_DATE = "election_date"; + protected static final String ELECTION_TYPE = "election_type"; + protected static final String PUBLIC_MEETING_DATE = "public_meeting_date"; + protected static final String STATUS = "status"; + protected static final String AUDIT = "audit"; + protected static final String CONTEST = "contest"; + protected static final String REASON = "reason"; + protected static final String NAME = "name"; + protected static final String ID = "id"; + @BeforeClass public void setup() { RestAssured.baseURI = "http://localhost"; @@ -134,8 +162,8 @@ protected void authenticate(final SessionFilter filter, final String user, final final String authStatus = response.getBody().jsonPath().getString("stage"); LOGGER.debug("Auth status for login " + user + " stage " + stage + " is " + authStatus); - assertEquals(authStatus, (stage == 1) ? "TRADITIONALLY_AUTHENTICATED" : - "SECOND_FACTOR_AUTHENTICATED", "Stage " + stage + " auth failed."); + assertEquals(authStatus, (stage == 1) ? TRADITIONALLY_AUTHENTICATED.toString() + : SECOND_FACTOR_AUTHENTICATED.toString(), "Stage " + stage + " auth failed."); } /** @@ -145,7 +173,7 @@ protected void authenticate(final SessionFilter filter, final String user, final * @param user Username to unauthenticate. */ protected void logout(final SessionFilter filter, final String user) { - final JSONObject requestParams = createBody(Map.of("username", user)); + final JSONObject requestParams = createBody(Map.of(USERNAME, user)); given() .filter(filter) @@ -282,10 +310,10 @@ protected void updateAuditInfo(final String canonicalListFile, final BigDecimal final SessionFilter filter = doLogin("stateadmin1"); final JSONObject requestParams = new JSONObject(); - requestParams.put("risk_limit", riskLimit); - requestParams.put("election_date","2024-09-15T05:42:17.796Z"); - requestParams.put("election_type","general"); - requestParams.put("public_meeting_date","2024-09-22T05:42:22.037Z"); + requestParams.put(RISK_LIMIT_JSON, riskLimit); + requestParams.put(ELECTION_DATE,"2024-09-15T05:42:17.796Z"); + requestParams.put(ELECTION_TYPE,"general"); + requestParams.put(PUBLIC_MEETING_DATE,"2024-09-22T05:42:22.037Z"); final JSONObject canonicalListContents = new JSONObject(); canonicalListContents.put("contents",readFromFile(canonicalListFile)); requestParams.put("upload_file", List.of(canonicalListContents)); @@ -313,7 +341,8 @@ protected boolean uploadSuccessfulWithin(int timeAllowedSeconds, final Set 0) { List succeededCounties = new ArrayList<>(); for (int c : counties) { - final String status = dashboard.getString("county_status." + c + "." + fileType + ".status"); + final String status = dashboard.getString(COUNTY_STATUS + "." + c + "." + fileType + "." + + STATUS); // Upload failed (e.g. a hash mismatch). if (status != null && status.equals(UploadedFile.FileStatus.FAILED.toString())) { return false; @@ -393,17 +422,17 @@ protected void targetContests(final Map targetedContestsWithReas // Find the IDs of the ones we want to target. for(int i=0 ; i < contests.getList("").size() ; i++) { - final String contestName = contests.getString("[" + i + "].name"); + final String contestName = contests.getString("[" + i + "]." + NAME); // If this contest's name is one of the targeted ones... final String reason = targetedContestsWithReasons.get(contestName); if(reason != null) { // add it to the selections. final JSONObject contestSelection = new JSONObject(); - final Integer contestId = contests.getInt("[" + i + "].id"); - contestSelection.put("audit","COMPARISON"); - contestSelection.put("contest",contestId); - contestSelection.put("reason", reason); + final Integer contestId = contests.getInt("[" + i + "]." + ID); + contestSelection.put(AUDIT, COMPARISON.toString()); + contestSelection.put(CONTEST, contestId); + contestSelection.put(REASON, reason); contestSelections.add(contestSelection); } } @@ -428,7 +457,7 @@ protected void setSeed(final String seed) { final SessionFilter filter = doLogin("stateadmin1"); final JSONObject requestParams = new JSONObject(); - requestParams.put("seed", seed); + requestParams.put(SEED, seed); given() .filter(filter) From ed345d71d3f5679b106fb30a9607b81089ef232d Mon Sep 17 00:00:00 2001 From: vteague Date: Sun, 10 Nov 2024 21:52:55 +1100 Subject: [PATCH 32/40] Code cleanup; disable workflow tests. --- .../corla/workflows/Demo1.java | 4 ++-- .../EstimateSampleSizesVaryingManifests.java | 4 ++-- .../corla/workflows/Workflow.java | 24 +++++++++---------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java index d0182a84..20f93d5c 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java @@ -43,7 +43,7 @@ * At the moment, this seems to run fine if run alone, but not to run in parallel with * EstimateSamplesVaryingManifests, or in * github actions. Hence currently disabled. */ -@Test(enabled=true) +@Test(enabled=false) public class Demo1 extends Workflow { /** @@ -85,7 +85,7 @@ public static void beforeAll() { * - demo1_loadCVRs, demo1_loadManifests, * - Boulder_loadCVRs, Boulder_loadManifest. */ - @Test(enabled = true) + @Test public void runDemo1() throws InterruptedException { testUtils.log(LOGGER, "Demo1"); diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java index e7c0aa4b..cc6834ce 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java @@ -55,7 +55,7 @@ * At the moment, this seems to run fine if run alone, but not to run in parallel with Demo1 or in * github actions. Hence currently disabled. */ -@Test(enabled=true) +@Test(enabled=false) public class EstimateSampleSizesVaryingManifests extends Workflow { /** @@ -94,7 +94,7 @@ public static void beforeAll() { /** * This "test" uploads CVRs and ballot manifests. */ - @Test(enabled = true) + @Test(enabled = false) public void runManifestVaryingDemo() throws InterruptedException { testUtils.log(LOGGER, "runManifestVaryingDemo"); diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java index fdff79c3..01820b08 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java @@ -88,27 +88,27 @@ public class Workflow extends TestClassWithDatabase { /** * Strings for colorado-rla JSON structures. */ - protected static final String CVR_FILETYPE = "cvr-export"; - protected static final String MANIFEST_FILETYPE = "ballot-manifest"; - protected static final String CVR_JSON = "cvr_export_file"; - protected static final String MANIFEST_JSON = "ballot_manifest_file"; protected static final String ASM_STATE = "asm_state"; + protected static final String AUDIT = "audit"; protected static final String AUDIT_INFO = "audit_info"; - protected static final String RISK_LIMIT_JSON = "risk_limit"; - protected static final String SEED = "seed"; - protected static final String CANONICAL_CONTESTS = "canonicalContests"; + protected static final String CONTEST = "contest"; protected static final String CANONICAL_CHOICES = "canonicalChoices"; + protected static final String CANONICAL_CONTESTS = "canonicalContests"; protected static final String COUNTY_STATUS = "county_status"; + protected static final String CVR_FILETYPE = "cvr-export"; + protected static final String CVR_JSON = "cvr_export_file"; protected static final String ESTIMATED_BALLOTS = "estimated_ballots_to_audit"; protected static final String ELECTION_DATE = "election_date"; protected static final String ELECTION_TYPE = "election_type"; + protected static final String ID = "id"; + protected static final String MANIFEST_FILETYPE = "ballot-manifest"; + protected static final String MANIFEST_JSON = "ballot_manifest_file"; + protected static final String NAME = "name"; protected static final String PUBLIC_MEETING_DATE = "public_meeting_date"; - protected static final String STATUS = "status"; - protected static final String AUDIT = "audit"; - protected static final String CONTEST = "contest"; protected static final String REASON = "reason"; - protected static final String NAME = "name"; - protected static final String ID = "id"; + protected static final String RISK_LIMIT_JSON = "risk_limit"; + protected static final String SEED = "seed"; + protected static final String STATUS = "status"; @BeforeClass public void setup() { From 460356748a1fa8c740f9b7837ace83aa130c33a7 Mon Sep 17 00:00:00 2001 From: vteague Date: Sun, 10 Nov 2024 22:02:11 +1100 Subject: [PATCH 33/40] Disable Demo1 at the test level. --- .../au/org/democracydevelopers/corla/workflows/Demo1.java | 4 ++-- .../corla/workflows/EstimateSampleSizesVaryingManifests.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java index 20f93d5c..1bf35298 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java @@ -43,7 +43,7 @@ * At the moment, this seems to run fine if run alone, but not to run in parallel with * EstimateSamplesVaryingManifests, or in * github actions. Hence currently disabled. */ -@Test(enabled=false) +@Test(enabled=true) public class Demo1 extends Workflow { /** @@ -85,7 +85,7 @@ public static void beforeAll() { * - demo1_loadCVRs, demo1_loadManifests, * - Boulder_loadCVRs, Boulder_loadManifest. */ - @Test + @Test(enabled=false) public void runDemo1() throws InterruptedException { testUtils.log(LOGGER, "Demo1"); diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java index cc6834ce..307c976b 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java @@ -55,7 +55,7 @@ * At the moment, this seems to run fine if run alone, but not to run in parallel with Demo1 or in * github actions. Hence currently disabled. */ -@Test(enabled=false) +@Test(enabled=true) public class EstimateSampleSizesVaryingManifests extends Workflow { /** From 8c162ecb2dcca442b79f0460828d046f167ff48e Mon Sep 17 00:00:00 2001 From: vteague Date: Fri, 15 Nov 2024 14:30:32 +1100 Subject: [PATCH 34/40] Update Github action. --- .github/workflows/maven.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 0d1372e1..37b01e6e 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -37,4 +37,5 @@ jobs: run: cd server/eclipse-project; mvn -Dtest='us.freeandfair.corla.**' test - name: IRV Tests + working-directory: server/eclipse-project run: cd server/eclipse-project; mvn -Dtest='au.org.democracydevelopers.corla.**' test From bca42b67a3fbfbe8d5b681e6335be309a1f99246 Mon Sep 17 00:00:00 2001 From: vteague Date: Fri, 15 Nov 2024 14:31:52 +1100 Subject: [PATCH 35/40] Re-enable Demo1 at the test level. --- .../java/au/org/democracydevelopers/corla/workflows/Demo1.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java index 1bf35298..8121842b 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java @@ -85,7 +85,7 @@ public static void beforeAll() { * - demo1_loadCVRs, demo1_loadManifests, * - Boulder_loadCVRs, Boulder_loadManifest. */ - @Test(enabled=false) + @Test(enabled=true) public void runDemo1() throws InterruptedException { testUtils.log(LOGGER, "Demo1"); From 7b7ce978b6bf82cb47647bc0307c905efea5961d Mon Sep 17 00:00:00 2001 From: vteague Date: Fri, 15 Nov 2024 14:39:07 +1100 Subject: [PATCH 36/40] Update workflow directory. --- .github/workflows/maven.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 37b01e6e..1654c4a2 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -38,4 +38,4 @@ jobs: - name: IRV Tests working-directory: server/eclipse-project - run: cd server/eclipse-project; mvn -Dtest='au.org.democracydevelopers.corla.**' test + run: mvn -Dtest='au.org.democracydevelopers.corla.**' test From df5acbb98afdc98467edb34e76cf6f4874abcf8a Mon Sep 17 00:00:00 2001 From: vteague Date: Fri, 15 Nov 2024 14:46:48 +1100 Subject: [PATCH 37/40] Better error diagnostics. --- .../au/org/democracydevelopers/corla/workflows/Workflow.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java index 01820b08..f79a65b1 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java @@ -129,10 +129,10 @@ protected static void runMain(final String testFileName) { config.store(sw, "Ephemeral database config for Demo1"); os.write(sw.toString().getBytes()); os.close(); - main(propertiesFile); } catch (Exception e) { - LOGGER.error("Couldn't write Demo1-test.properties", e); + LOGGER.error("Couldn't write Demo1-test.properties. "+e.getMessage(), e); } + main(propertiesFile); } protected JSONObject createBody(final Map data) { From f80efb7fbcf8eb73a9d9225ae47db694d8b55a5c Mon Sep 17 00:00:00 2001 From: vteague Date: Fri, 15 Nov 2024 15:01:02 +1100 Subject: [PATCH 38/40] Add test properties file, to help diagnose workflow actions errors. --- .../src/test/workflows/temp/Demo1-test.properties | 1 + 1 file changed, 1 insertion(+) create mode 100644 server/eclipse-project/src/test/workflows/temp/Demo1-test.properties diff --git a/server/eclipse-project/src/test/workflows/temp/Demo1-test.properties b/server/eclipse-project/src/test/workflows/temp/Demo1-test.properties new file mode 100644 index 00000000..b3bcd957 --- /dev/null +++ b/server/eclipse-project/src/test/workflows/temp/Demo1-test.properties @@ -0,0 +1 @@ +#Placeholder database config for Demo1 From 1c76f4ca199e2bc7ee6dd97d7e8cd6df85324555 Mon Sep 17 00:00:00 2001 From: vteague Date: Fri, 15 Nov 2024 15:25:16 +1100 Subject: [PATCH 39/40] Enable EstimateSampleSizesVaryingManifests test. --- .../java/au/org/democracydevelopers/corla/workflows/Demo1.java | 2 ++ .../corla/workflows/EstimateSampleSizesVaryingManifests.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java index 8121842b..c3f17813 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Demo1.java @@ -172,6 +172,8 @@ public void runDemo1() throws InterruptedException { assertFalse(sampleSizes.isEmpty()); // TODO get assertions, sanity check, but first get the raire-service working (see above). + + LOGGER.debug("Successfully completed Demo1."); } } diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java index 307c976b..e168c28f 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/EstimateSampleSizesVaryingManifests.java @@ -94,7 +94,7 @@ public static void beforeAll() { /** * This "test" uploads CVRs and ballot manifests. */ - @Test(enabled = false) + @Test(enabled=true) public void runManifestVaryingDemo() throws InterruptedException { testUtils.log(LOGGER, "runManifestVaryingDemo"); From 37b8d5da0cfb7087abf3cab47fea9f9d70bfe8ff Mon Sep 17 00:00:00 2001 From: vteague Date: Thu, 21 Nov 2024 10:09:04 +1100 Subject: [PATCH 40/40] Improvements based on @michelleblom's review. --- .../endpoint/AbstractAllIrvEndpoint.java | 2 +- .../corla/endpoint/EstimateSampleSizes.java | 4 ++ .../corla/controller/ContestCounter.java | 39 +++++++++++-------- .../corla/endpoint/StartAuditRound.java | 1 + .../ComparisonAuditControllerTests.java | 3 +- .../corla/workflows/Workflow.java | 6 +-- .../test/workflows/temp/Demo1-test.properties | 12 +++++- 7 files changed, 45 insertions(+), 22 deletions(-) diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java index a38d64b2..15030091 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/AbstractAllIrvEndpoint.java @@ -101,9 +101,9 @@ protected void reset() { * to the IRV ones. Although the countContest function isn't really useful or meaningful for IRV, * it is called here because it actually does a lot of other useful things, such as setting the * number of allowed winners and gathering all the results across counties. + * Assumption: Contest names are unique. * @return A list of all ContestResults for IRV contests. * @throws RuntimeException if it encounters contests with a mix of IRV and any other contest type. - * Assumption: Contest names are unique. */ protected static List getIRVContestResults() { final String prefix = "[getIRVContestResults]"; diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java index 8a88ca64..5c247eb7 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/EstimateSampleSizes.java @@ -128,6 +128,9 @@ public String endpointBody(final Request the_request, final Response the_respons /** * Compute sample sizes for all contests for which CountyContestResults exist in the database. + * This method ignores manifests, instead using the count of uploaded CSVs. This means that the + * estimate may differ from the estimate computed by estimatedSampleSize() during the audit, if + * the manifest has more votes than the CVR file. * @return A list of string arrays containing rows with the following data: county name, * contest name, contest type, single or multi-jurisdictional, ballots cast, diluted margin, * and estimated sample size. @@ -143,6 +146,7 @@ public String estimateSampleSizes() { // in a ContestResult for an IRV contest will not be used. In the call to ContestCounter // (countAllContests), all persisted CountyContestResults will be accessed from the database, // grouped by contest, and accumulated into a single ContestResult. + // Set the useManifests flag to false, to tell contest counter to use CVR count instead. final List countedCRs = ContestCounter.countAllContests(false).stream().peek(cr -> cr.setAuditReason(AuditReason.OPPORTUNISTIC_BENEFITS)).toList(); diff --git a/server/eclipse-project/src/main/java/us/freeandfair/corla/controller/ContestCounter.java b/server/eclipse-project/src/main/java/us/freeandfair/corla/controller/ContestCounter.java index 6c2807d5..0560bdd0 100644 --- a/server/eclipse-project/src/main/java/us/freeandfair/corla/controller/ContestCounter.java +++ b/server/eclipse-project/src/main/java/us/freeandfair/corla/controller/ContestCounter.java @@ -30,10 +30,14 @@ private ContestCounter() { /** * Group all CountyContestResults by contest name and tally the votes * across all counties that have reported results. - * This only works for plurality - not valid, and not needed, for IRV. - * - * @return List A high level view of contests and their - * participants. + * If 'useManifests' is true, it calculates the total universe size from the uploaded manifests - + * this is important for the validity of the audit step. useManifests can be false for sample-size + * estimation, where we expect that counties may not have uploaded valid manifests - in this case, + * universe size is calculated by counting the CVRs. + * The actual tallying is valid only for plurality - it is not valid, and not needed, for IRV. + * However, this function may still be useful for IRV, e.g. for gathering contests together by + * name and calculating their universes. + * @return List A high level view of contests and their participants. */ public static List countAllContests(boolean useManifests) { return @@ -82,8 +86,8 @@ public static Set pairwiseMargins(final Set winners, * @param countyContestResults the county-by-county contest results, which are useful for plurality. * @param useManifests whether to use manifests to compute the total number of ballots. This * *must* be true when counting for audits - it can be false only when - * doing sample size estimation. In this case, it computes the total number - * of ballots based on the (untrusted) CVRs. + * doing pre-audit sample size estimation. In this case, it computes + * the total number of ballots based on the (untrusted) CVRs. **/ public static ContestResult countContest(final Map.Entry> countyContestResults, boolean useManifests) { @@ -125,12 +129,13 @@ public static ContestResult countContest(final Map.Entry cr.county()) .collect(Collectors.toSet())); - Long ballotCount; - if(useManifests) { - ballotCount = BallotManifestInfoQueries.totalBallots(contestResult.countyIDs()); - } else { - ballotCount = countCVRs(contestResult); - } + // If we are supposed to use manifests, set the ballotCount to their indicated total, otherwise + // count the CVRs. + final Long ballotCount = useManifests ? + BallotManifestInfoQueries.totalBallots(contestResult.countyIDs()) : countCVRs(contestResult); + LOGGER.debug(String.format("%s Contest %s counted %s manifests.", "[countContest]", contestName, + useManifests ? "with" : "without")); + final Set margins = pairwiseMargins(contestResult.getWinners(), contestResult.getLosers(), voteTotals); @@ -154,12 +159,14 @@ public static ContestResult countContest(final Map.Entry countAndSaveContests(final Set cta) { LOGGER.debug(String.format("[countAndSaveContests: cta=%s]", cta)); final Map tcr = targetedContestReasons(cta); + // Count the contests, using the trusted Manifests to get the universe sizes. return ContestCounter.countAllContests(true).stream().map(cr -> { cr.setAuditReason(tcr.getOrDefault(cr.getContestName(), AuditReason.OPPORTUNISTIC_BENEFITS)); diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/controller/ComparisonAuditControllerTests.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/controller/ComparisonAuditControllerTests.java index 2fec25f4..455eccbd 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/controller/ComparisonAuditControllerTests.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/controller/ComparisonAuditControllerTests.java @@ -99,7 +99,8 @@ public static void afterAll() { public void IRVContestMakesIRVAudit() { testUtils.log(LOGGER, "IRVContestMakesIRVAudit"); - // Set up the contest results from the stored data. + // Set up the contest results from the stored data. Use manifests for this test (though it + // shouldn't matter because for this sample data, the manifests and CVRs have the same totals). List results = ContestCounter.countAllContests(true); // Find all the ContestResults for TinyIRV - there should be one. diff --git a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java index f79a65b1..bff3e69a 100644 --- a/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java +++ b/server/eclipse-project/src/test/java/au/org/democracydevelopers/corla/workflows/Workflow.java @@ -122,15 +122,15 @@ public void setup() { * @param testFileName the name of the test file - must be different for each test. */ protected static void runMain(final String testFileName) { - final String propertiesFile = tempConfigPath +testFileName+"-test.properties"; + final String propertiesFile = tempConfigPath + testFileName + "-test.properties"; try { FileOutputStream os = new FileOutputStream(propertiesFile); final StringWriter sw = new StringWriter(); - config.store(sw, "Ephemeral database config for Demo1"); + config.store(sw, "Ephemeral database config for "+testFileName); os.write(sw.toString().getBytes()); os.close(); } catch (Exception e) { - LOGGER.error("Couldn't write Demo1-test.properties. "+e.getMessage(), e); + LOGGER.error("Couldn't write " + testFileName + "-test.properties. "+e.getMessage(), e); } main(propertiesFile); } diff --git a/server/eclipse-project/src/test/workflows/temp/Demo1-test.properties b/server/eclipse-project/src/test/workflows/temp/Demo1-test.properties index b3bcd957..c293502d 100644 --- a/server/eclipse-project/src/test/workflows/temp/Demo1-test.properties +++ b/server/eclipse-project/src/test/workflows/temp/Demo1-test.properties @@ -1 +1,11 @@ -#Placeholder database config for Demo1 +#Ephemeral database config for Demo1 +#Fri Nov 15 15:26:53 AEDT 2024 +raire_url=http\://localhost\:8080 +generate_assertions_mock_port=8110 +hibernate.driver=org.postgresql.Driver +hibernate.pass=corlasecret +hibernate.dialect=org.hibernate.dialect.PostgreSQL9Dialect +get_assertions_mock_port=8111 +hibernate.show_sql=false +hibernate.user=corlaadmin +hibernate.url=jdbc\:postgresql\://localhost\:32774/corla?loggerLevel\=OFF