diff --git a/.github/actions/zip/action.yml b/.github/actions/zip/action.yml index 9bc976931..089fd219b 100644 --- a/.github/actions/zip/action.yml +++ b/.github/actions/zip/action.yml @@ -27,4 +27,14 @@ runs: - name: "Generate zip for Windows" if: runner.os == 'Windows' shell: powershell - run: Compress-Archive -Path ${{inputs.input}} -Destination ${{inputs.zipFilename}} + run: | + $inputPath = "${{inputs.input}}" + # Git Bash on Windows emits Unix-style paths like /c/Users/... instead of C:\Users\... + if ($inputPath -match '^/([a-zA-Z])/(.+)$') { + # Convert /c/some/path -> C:\some\path + $inputPath = $Matches[1].ToUpper() + ':' + ($Matches[2] -replace '/', '\') + } else { + # Expand ~ since PowerShell cmdlets don't resolve it in string arguments + $inputPath = $inputPath -replace '^~', $HOME + } + Compress-Archive -Path $inputPath -Destination ${{inputs.zipFilename}} diff --git a/.github/workflows/generate-dependency-hashes.sh b/.github/workflows/generate-dependency-hashes.sh index e36220fe1..7cb808422 100755 --- a/.github/workflows/generate-dependency-hashes.sh +++ b/.github/workflows/generate-dependency-hashes.sh @@ -9,7 +9,7 @@ os=$1 parentPath=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) echo "Filename, SHA-1 Checksum, SHA-256 Checksum, Maven Dependency URL, Direct URL to SHA-1, Direct URL to SHA-256" -cd ~/.gradle/caches/modules-2/files-2.1 +cd "${GRADLE_USER_HOME:-$HOME/.gradle}/caches/modules-2/files-2.1" for filename in $(find * -type f); do # filename is of format, with dot-separated org: # ////-. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 215e5496d..c534f8898 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -108,13 +108,13 @@ jobs: run: echo "FILEPATH=build/${{ steps.basefn.outputs.FILEPATH }}${{ steps.ext.outputs.EXT }}" >> $GITHUB_OUTPUT - name: "Set up JDK 21.0.7" - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: java-version: '21.0.7' distribution: 'temurin' - - name: "Validate Gradle wrapper" - uses: gradle/actions/wrapper-validation@v3 + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v6 - name: "Create zip with jlinkZip" uses: ./.github/actions/gradle-and-sha @@ -140,12 +140,17 @@ jobs: shell: bash run: ./.github/workflows/generate-dependency-hashes.sh ${{ runner.os }} >> ${{steps.checksumsfn.outputs.FILEPATH}} + - name: "Get Gradle user home" + id: gradle-home + shell: bash + run: echo "PATH=${GRADLE_USER_HOME:-$HOME/.gradle}" >> $GITHUB_OUTPUT + - name: "Create dependency zip" uses: ./.github/actions/zip with: # Build, then remove all non-essential files command: ./gradlew assemble && ./gradlew --stop - input: "~/.gradle/caches" + input: "${{ steps.gradle-home.outputs.PATH }}/caches" zipFilename: ${{steps.cachefn.outputs.FILEPATH}} - name: "Generate SHA512 for plugins cache" diff --git a/src/main/java/network/brightspots/rcv/ClearBallotCvrReader.java b/src/main/java/network/brightspots/rcv/ClearBallotCvrReader.java index ace6183eb..2b08e7601 100644 --- a/src/main/java/network/brightspots/rcv/ClearBallotCvrReader.java +++ b/src/main/java/network/brightspots/rcv/ClearBallotCvrReader.java @@ -108,7 +108,7 @@ void readCastVoteRecords(List castVoteRecords) new CastVoteRecord( source.getContestId(), cvrData[CvrColumnField.ScanComputerName.ordinal()], - null, + cvrData[CvrColumnField.BoxID.ordinal()], cvrData[CvrColumnField.BallotID.ordinal()], cvrData[CvrColumnField.PrecinctID.ordinal()], null, diff --git a/src/main/java/network/brightspots/rcv/ContestConfig.java b/src/main/java/network/brightspots/rcv/ContestConfig.java index 9334b3b0f..a7333b729 100644 --- a/src/main/java/network/brightspots/rcv/ContestConfig.java +++ b/src/main/java/network/brightspots/rcv/ContestConfig.java @@ -44,7 +44,6 @@ class ContestConfig { // If any booleans are unspecified in config file, they should default to false no matter what - static final String AUTOMATED_TEST_VERSION = "TEST"; static final String SUGGESTED_OUTPUT_DIRECTORY = "output"; static final boolean SUGGESTED_TABULATE_BY_BATCH = false; static final boolean SUGGESTED_TABULATE_BY_PRECINCT = false; @@ -58,7 +57,6 @@ class ContestConfig { static final boolean SUGGESTED_CONTINUE_UNTIL_TWO_CANDIDATES_REMAIN = false; static final boolean SUGGESTED_EXHAUST_ON_DUPLICATE_CANDIDATES = false; static final boolean SUGGESTED_FIRST_ROUND_DETERMINES_THRESHOLD = false; - static final boolean SUGGESTED_TREAT_BLANK_AS_UNDECLARED_WRITE_IN = false; static final int SUGGESTED_CVR_FIRST_VOTE_COLUMN = 4; static final int SUGGESTED_CVR_FIRST_VOTE_ROW = 2; static final int SUGGESTED_CVR_ID_COLUMN = 1; @@ -67,7 +65,7 @@ class ContestConfig { static final int SUGGESTED_MAX_SKIPPED_RANKS_ALLOWED = 1; static final boolean SUGGESTED_MAX_SKIPPED_RANKS_ALLOWED_UNLIMITED = false; static final String SUGGESTED_OVERVOTE_LABEL = "overvote"; - static final String SUGGESTED_SKIPPED_RANK_LABEL = "undervote"; + static final String SUGGESTED_UWI_LABEL = ""; // note: non-blank UWI labels creates UWI candidates static final String MAX_SKIPPED_RANKS_ALLOWED_UNLIMITED_OPTION = "unlimited"; static final String MAX_RANKINGS_ALLOWED_NUM_CANDIDATES_OPTION = "max"; private static final int MIN_COLUMN_INDEX = 1; @@ -109,12 +107,22 @@ private ContestConfig(RawContestConfig rawConfig, String sourceDirectory) { static ContestConfig loadContestConfig(RawContestConfig rawConfig, String sourceDirectory) { ContestConfig config = new ContestConfig(rawConfig, sourceDirectory); + try { - config.processCandidateData(); - } catch (Exception exception) { - Logger.severe("Error processing candidate data:\n%s", exception); + ContestConfigMigration.migrateConfigVersion(config); + } catch (ContestConfigMigration.ConfigVersionIsNewerThanAppVersionException exception) { + Logger.severe("Error migrating config to current version:\n%s", exception); config = null; } + + if (config != null) { + try { + config.processCandidateData(); + } catch (Exception exception) { + Logger.severe("Error processing candidate data:\n%s", exception); + config = null; + } + } return config; } @@ -148,6 +156,11 @@ static ContestConfig loadContestConfig(String configPath) { return loadContestConfig(configPath, false); } + static boolean isConfigFileInTestDir(String filepath) { + final String regex = "^.*brightspots.rcv.test_data.([^/\\\\]+).\\1_config\\.json$"; + return filepath.matches(regex); + } + /* Performs basic validation on CVR sources and returns a set of validation errors. **/ static Set performBasicCvrSourceValidation(CvrSource source) { Set validationErrors = new HashSet<>(); @@ -158,16 +171,18 @@ static Set performBasicCvrSourceValidation(CvrSource source) { if (!isNullOrBlank(source.getOvervoteLabel()) && stringAlreadyInUseElsewhereInSource( source.getOvervoteLabel(), source, "overvoteLabel")) { + Logger.severe( + "Overvote label must be defined and unique from other labels for CVR source: %s", + source.getFilePath()); validationErrors.add(ValidationError.CVR_OVERVOTE_LABEL_INVALID); } - if (!isNullOrBlank(source.getSkippedRankLabel()) - && stringAlreadyInUseElsewhereInSource( - source.getSkippedRankLabel(), source, "skippedRankLabel")) { - validationErrors.add(ValidationError.CVR_SKIPPED_RANK_LABEL_INVALID); - } if (!isNullOrBlank(source.getUndeclaredWriteInLabel()) && stringAlreadyInUseElsewhereInSource( source.getUndeclaredWriteInLabel(), source, "undeclaredWriteInLabel")) { + Logger.severe( + "Undeclared write-in label must be defined and unique" + + " from other labels for CVR source: %s", + source.getFilePath()); validationErrors.add(ValidationError.CVR_UWI_LABEL_INVALID); } @@ -286,21 +301,6 @@ && stringAlreadyInUseElsewhereInSource( source.getOvervoteDelimiter(), "overvoteDelimiter", provider, source.getFilePath())) { validationErrors.add(ValidationError.CVR_OVERVOTE_DELIMITER_UNEXPECTEDLY_DEFINED); } - - if (fieldIsDefinedButShouldNotBeForProvider( - source.getSkippedRankLabel(), "skippedRankLabel", provider, source.getFilePath())) { - validationErrors.add(ValidationError.CVR_SKIPPED_RANK_LABEL_UNEXPECTEDLY_DEFINED); - } - - if (source.getTreatBlankAsUndeclaredWriteIn()) { - logErrorWithLocation( - String.format( - "treatBlankAsUndeclaredWriteIn should not be true for CVR source with " - + "provider \"%s\"", - provider), - source.getFilePath()); - validationErrors.add(ValidationError.CVR_TREAT_BLANK_AS_UWI_UNEXPECTEDLY_TRUE); - } } boolean providerRequiresContestId = @@ -484,8 +484,6 @@ private static boolean stringAlreadyInUseElsewhereInSource( if (!inUse) { inUse = stringMatchesAnotherFieldValue(string, field, source.getOvervoteLabel(), "overvoteLabel") - || stringMatchesAnotherFieldValue( - string, field, source.getSkippedRankLabel(), "skippedRankLabel") || stringMatchesAnotherFieldValue( string, field, source.getUndeclaredWriteInLabel(), "undeclaredWriteInLabel"); } @@ -534,13 +532,9 @@ private void validateTabulatorVersion() { if (isNullOrBlank(getTabulatorVersion())) { validationErrors.add(ValidationError.TABULATOR_VERSION_MISSING); Logger.severe("tabulatorVersion is required!"); - } else { - // ignore this check for test data, but otherwise require version to match current app version - if (!getTabulatorVersion().equals(AUTOMATED_TEST_VERSION) - && !getTabulatorVersion().equals(Main.APP_VERSION)) { - validationErrors.add(ValidationError.TABULATOR_VERSION_NOT_SUPPORTED); - Logger.severe("tabulatorVersion %s not supported!", getTabulatorVersion()); - } + } else if (!getTabulatorVersion().equals(Main.APP_VERSION)) { + validationErrors.add(ValidationError.TABULATOR_VERSION_NOT_SUPPORTED); + Logger.severe("tabulatorVersion %s not supported!", getTabulatorVersion()); } if (validationErrors.contains(ValidationError.TABULATOR_VERSION_MISSING) || validationErrors.contains(ValidationError.TABULATOR_VERSION_NOT_SUPPORTED)) { @@ -1165,7 +1159,7 @@ Integer getStopTabulationEarlyAfterRound() { int getNumDeclaredCandidates() { int size = getCandidateNames().size(); - if (undeclaredWriteInsEnabled()) { + if (undeclaredWriteInsExplicitlyEnabled()) { // we subtract one for UNDECLARED_WRITE_IN_OUTPUT_LABEL; size = size - 1; } @@ -1267,24 +1261,25 @@ private void processCandidateData() { } // If any of the sources support undeclared write-ins, we need to recognize them as a valid - // "candidate" option. - if (undeclaredWriteInsEnabled()) { + // "candidate" option. Note: it's possible that + if (undeclaredWriteInsExplicitlyEnabled()) { candidateNames.add(Tabulator.UNDECLARED_WRITE_IN_OUTPUT_LABEL); candidateAliasesToNameMap.put( Tabulator.UNDECLARED_WRITE_IN_OUTPUT_LABEL, Tabulator.UNDECLARED_WRITE_IN_OUTPUT_LABEL); } } - private boolean undeclaredWriteInsEnabled() { - boolean includeUwi = false; + /** + * Note: it is possible for UWIs to be _implictly_ declared, e.g., if images are found in ES&S + * .XLSXs, we assume they are UWIs. + */ + private boolean undeclaredWriteInsExplicitlyEnabled() { for (CvrSource source : rawConfig.cvrFileSources) { - if (!isNullOrBlank(source.getUndeclaredWriteInLabel()) - || source.getTreatBlankAsUndeclaredWriteIn()) { - includeUwi = true; - break; + if (!isNullOrBlank(source.getUndeclaredWriteInLabel())) { + return true; } } - return includeUwi; + return false; } // Possible validation errors @@ -1296,7 +1291,6 @@ enum ValidationError { CVR_NO_FILES_SPECIFIED, CVR_FILE_PATH_MISSING, CVR_OVERVOTE_LABEL_INVALID, - CVR_SKIPPED_RANK_LABEL_INVALID, CVR_UWI_LABEL_INVALID, CVR_PROVIDER_INVALID, CVR_FIRST_VOTE_COLUMN_INVALID, @@ -1306,7 +1300,6 @@ enum ValidationError { CVR_PRECINCT_COLUMN_INVALID, CVR_OVERVOTE_DELIMITER_INVALID, CVR_CDF_FILE_PATH_INVALID, - CVR_TREAT_BLANK_AS_UWI_UNEXPECTEDLY_TRUE, CVR_CONTEST_ID_INVALID, CVR_DUPLICATE_FILE_PATHS, CVR_FILE_PATH_INVALID, @@ -1323,7 +1316,6 @@ enum ValidationError { CVR_ID_COLUMN_UNEXPECTEDLY_DEFINED, CVR_BATCH_COLUMN_UNEXPECTEDLY_DEFINED, CVR_PRECINCT_COLUMN_UNEXPECTEDLY_DEFINED, - CVR_SKIPPED_RANK_LABEL_UNEXPECTEDLY_DEFINED, CVR_CONTEST_ID_UNEXPECTEDLY_DEFINED, CVR_OUTPUT_NOT_ALLOWED_IN_USER_DIRECTORY, CANDIDATE_NAME_MISSING, diff --git a/src/main/java/network/brightspots/rcv/ContestConfigMigration.java b/src/main/java/network/brightspots/rcv/ContestConfigMigration.java index 64ac8ddca..57857c427 100644 --- a/src/main/java/network/brightspots/rcv/ContestConfigMigration.java +++ b/src/main/java/network/brightspots/rcv/ContestConfigMigration.java @@ -26,6 +26,7 @@ import network.brightspots.rcv.RawContestConfig.CvrSource; import network.brightspots.rcv.Tabulator.TiebreakMode; import network.brightspots.rcv.Tabulator.WinnerElectionMode; +import org.apache.poi.util.StringUtil; final class ContestConfigMigration { private ContestConfigMigration() {} @@ -57,126 +58,148 @@ static boolean isConfigVersionNewerThanAppVersion(String configVersion) { static void migrateConfigVersion(ContestConfig config) throws ConfigVersionIsNewerThanAppVersionException { - String version = config.rawConfig.tabulatorVersion; + String originalVersion = config.rawConfig.tabulatorVersion; boolean needsMigration = - version == null - || (!version.equals(Main.APP_VERSION) - && !version.equals(ContestConfig.AUTOMATED_TEST_VERSION)); - if (needsMigration) { - if (isConfigVersionNewerThanAppVersion(version)) { - throw new ConfigVersionIsNewerThanAppVersionException(); - } + originalVersion == null + || (!originalVersion.equals(Main.APP_VERSION)); + if (!needsMigration) { + return; + } - // Any necessary future version migration logic goes here - RawContestConfig rawConfig = config.getRawConfig(); - ContestRules rules = rawConfig.rules; - - if (config.getWinnerElectionMode() == WinnerElectionMode.MODE_UNKNOWN) { - String oldWinnerElectionMode = rules.winnerElectionMode; - switch (oldWinnerElectionMode) { - case "standard" -> rules.winnerElectionMode = - config.getNumberOfWinners() > 1 - ? WinnerElectionMode.MULTI_SEAT_ALLOW_MULTIPLE_WINNERS_PER_ROUND - .getInternalLabel() - : WinnerElectionMode.STANDARD_SINGLE_WINNER.getInternalLabel(); - case "singleSeatContinueUntilTwoCandidatesRemain" -> { - rules.winnerElectionMode = WinnerElectionMode.STANDARD_SINGLE_WINNER.getInternalLabel(); - rules.continueUntilTwoCandidatesRemain = true; - } - case "multiSeatAllowOnlyOneWinnerPerRound" -> rules.winnerElectionMode = - WinnerElectionMode.MULTI_SEAT_ALLOW_ONLY_ONE_WINNER_PER_ROUND.getInternalLabel(); - case "multiSeatBottomsUp" -> rules.winnerElectionMode = - config.getNumberOfWinners() == 0 - || config.getMultiSeatBottomsUpPercentageThreshold() != null - ? WinnerElectionMode.MULTI_SEAT_BOTTOMS_UP_USING_PERCENTAGE_THRESHOLD - .getInternalLabel() - : WinnerElectionMode.MULTI_SEAT_BOTTOMS_UP_UNTIL_N_WINNERS.getInternalLabel(); - case "multiSeatSequentialWinnerTakesAll" -> rules.winnerElectionMode = - WinnerElectionMode.MULTI_SEAT_SEQUENTIAL_WINNER_TAKES_ALL.getInternalLabel(); - default -> { - Logger.warning( - "winnerElectionMode \"%s\" is unrecognized! Supply a valid " - + "winnerElectionMode.", - oldWinnerElectionMode); - rules.winnerElectionMode = null; - } - } - } + if (isConfigVersionNewerThanAppVersion(originalVersion)) { + throw new ConfigVersionIsNewerThanAppVersionException(); + } - if (config.getTiebreakMode() == TiebreakMode.MODE_UNKNOWN) { - Map tiebreakModeMigrationMap = - Map.of( - "random", - TiebreakMode.RANDOM, - "interactive", - TiebreakMode.INTERACTIVE, - "previousRoundCountsThenRandom", - TiebreakMode.PREVIOUS_ROUND_COUNTS_THEN_RANDOM, - "previousRoundCountsThenInteractive", - TiebreakMode.PREVIOUS_ROUND_COUNTS_THEN_INTERACTIVE, - "usePermutationInConfig", - TiebreakMode.USE_PERMUTATION_IN_CONFIG, - "generatePermutation", - TiebreakMode.GENERATE_PERMUTATION); - String oldTiebreakMode = rules.tiebreakMode; - if (tiebreakModeMigrationMap.containsKey(oldTiebreakMode)) { - rules.tiebreakMode = tiebreakModeMigrationMap.get(oldTiebreakMode).getInternalLabel(); - } else { - Logger.warning( - "tiebreakMode \"%s\" is unrecognized! Supply a valid tiebreakMode.", oldTiebreakMode); - rules.tiebreakMode = null; - } - } + if (originalVersion == null || isVersionNewer("2.0.2", originalVersion)) { + migrateUnversionedTo202(config); + } - // These four fields were previously at the config level, but are now set on a per-source - // basis. + if (isVersionNewer("2.1.0", originalVersion)) { + migrate201To210(config); + } - if (!isNullOrBlank(rules.overvoteLabel)) { - for (CvrSource source : rawConfig.cvrFileSources) { - source.setOvervoteLabel(rules.overvoteLabel); - } - } + // Always bump to the current app version, even if there is no change in config fields. + if (!Main.APP_VERSION.equals(config.rawConfig.tabulatorVersion)) { + config.rawConfig.tabulatorVersion = Main.APP_VERSION; + } - if (!isNullOrBlank(rules.undervoteLabel)) { - for (CvrSource source : rawConfig.cvrFileSources) { - source.setSkippedRankLabel(rules.undervoteLabel); - } - } + Logger.info( + "Migrated tabulator config version from %s to %s.", + originalVersion != null ? originalVersion : "unknown", + config.rawConfig.tabulatorVersion); + } - if (!isNullOrBlank(rules.undeclaredWriteInLabel)) { - for (CvrSource source : rawConfig.cvrFileSources) { - source.setUndeclaredWriteInLabel(rules.undeclaredWriteInLabel); - } + private static void migrate201To210(ContestConfig config) { + RawContestConfig rawConfig = config.getRawConfig(); + + for (CvrSource source : rawConfig.cvrFileSources) { + if (StringUtil.isNotBlank(source.getSkippedRankLabel()) + && ContestConfig.Provider.ESS + == ContestConfig.Provider.getByInternalLabel(source.getProvider()) + && !"undervote".equals(source.getSkippedRankLabel())) { + Logger.warning( + "Migrating config to v2.1.0 - the previously configured undervote label " + + "\"" + source.getSkippedRankLabel() + "\"" + + " is not compatible with the ES&S default of \"undervote\". Please review your " + + "CVR to ensure undervotes are properly identified with \"undervote\"."); + source.setSkippedRankLabel(null); } + } + + config.rawConfig.tabulatorVersion = "2.1.0"; + } - if (rules.treatBlankAsUndeclaredWriteIn) { - for (CvrSource source : rawConfig.cvrFileSources) { - source.setTreatBlankAsUndeclaredWriteIn(rules.treatBlankAsUndeclaredWriteIn); + private static void migrateUnversionedTo202(ContestConfig config) { + RawContestConfig rawConfig = config.getRawConfig(); + ContestRules rules = rawConfig.rules; + + if (config.getWinnerElectionMode() == WinnerElectionMode.MODE_UNKNOWN) { + String oldWinnerElectionMode = rules.winnerElectionMode; + if (oldWinnerElectionMode == null) { + Logger.severe("winnerElectionMode is required but was not found in the config! " + + "Supply a valid winnerElectionMode."); + return; + } + switch (oldWinnerElectionMode) { + case "standard" -> rules.winnerElectionMode = + config.getNumberOfWinners() > 1 + ? WinnerElectionMode.MULTI_SEAT_ALLOW_MULTIPLE_WINNERS_PER_ROUND + .getInternalLabel() + : WinnerElectionMode.STANDARD_SINGLE_WINNER.getInternalLabel(); + case "singleSeatContinueUntilTwoCandidatesRemain" -> { + rules.winnerElectionMode = WinnerElectionMode.STANDARD_SINGLE_WINNER.getInternalLabel(); + rules.continueUntilTwoCandidatesRemain = true; + } + case "multiSeatAllowOnlyOneWinnerPerRound" -> rules.winnerElectionMode = + WinnerElectionMode.MULTI_SEAT_ALLOW_ONLY_ONE_WINNER_PER_ROUND.getInternalLabel(); + case "multiSeatBottomsUp" -> rules.winnerElectionMode = + config.getNumberOfWinners() == 0 + || config.getMultiSeatBottomsUpPercentageThreshold() != null + ? WinnerElectionMode.MULTI_SEAT_BOTTOMS_UP_USING_PERCENTAGE_THRESHOLD + .getInternalLabel() + : WinnerElectionMode.MULTI_SEAT_BOTTOMS_UP_UNTIL_N_WINNERS + .getInternalLabel(); + case "multiSeatSequentialWinnerTakesAll" -> rules.winnerElectionMode = + WinnerElectionMode.MULTI_SEAT_SEQUENTIAL_WINNER_TAKES_ALL.getInternalLabel(); + default -> { + Logger.warning( + "winnerElectionMode \"%s\" is unrecognized! Supply a valid " + + "winnerElectionMode.", + oldWinnerElectionMode); + rules.winnerElectionMode = null; } } + } - // Migrations from 1.3.0 to 1.4.0 - if (rules.stopTabulationEarlyAfterRound == null) { - rules.stopTabulationEarlyAfterRound = ""; + if (config.getTiebreakMode() == TiebreakMode.MODE_UNKNOWN) { + Map tiebreakModeMigrationMap = + Map.of( + "random", + TiebreakMode.RANDOM, + "interactive", + TiebreakMode.INTERACTIVE, + "previousRoundCountsThenRandom", + TiebreakMode.PREVIOUS_ROUND_COUNTS_THEN_RANDOM, + "previousRoundCountsThenInteractive", + TiebreakMode.PREVIOUS_ROUND_COUNTS_THEN_INTERACTIVE, + "usePermutationInConfig", + TiebreakMode.USE_PERMUTATION_IN_CONFIG, + "generatePermutation", + TiebreakMode.GENERATE_PERMUTATION); + String oldTiebreakMode = rules.tiebreakMode; + if (tiebreakModeMigrationMap.containsKey(oldTiebreakMode)) { + rules.tiebreakMode = tiebreakModeMigrationMap.get(oldTiebreakMode).getInternalLabel(); + } else { + Logger.warning("tiebreakMode \"%s\" is unrecognized! Supply a valid tiebreakMode.", + oldTiebreakMode); + rules.tiebreakMode = null; } + } + + // These fields were previously at the config level, but are now set on a per-source basis. + // They used to include undervoteLabel and treatBlankAsUndeclaredWriteIn, both of which were + // removed in 2.1.0, so by ignoring them we effectively delete them. + // That means the output of this function is not exactly compatible with 2.0.2, but rather, + // a correct stepping stone while migrating up to the current app version. + + if (!isNullOrBlank(rules.overvoteLabel)) { for (CvrSource source : rawConfig.cvrFileSources) { - if (source.getUndervoteLabel() != null) { - if (source.getSkippedRankLabel() != null) { - Logger.severe("Config contains a deprecated field \"%s\". Ignoring.", - source.getUndervoteLabel()); - } else { - source.setSkippedRankLabel(source.getUndervoteLabel()); - } - } + source.setOvervoteLabel(rules.overvoteLabel); } + } - Logger.info( - "Migrated tabulator config version from %s to %s.", - config.rawConfig.tabulatorVersion != null ? config.rawConfig.tabulatorVersion : "unknown", - Main.APP_VERSION); + if (!isNullOrBlank(rules.undeclaredWriteInLabel)) { + for (CvrSource source : rawConfig.cvrFileSources) { + source.setUndeclaredWriteInLabel(rules.undeclaredWriteInLabel); + } + } - config.rawConfig.tabulatorVersion = Main.APP_VERSION; + // Migrations from 1.3.0 to 1.4.0 + if (rules.stopTabulationEarlyAfterRound == null) { + rules.stopTabulationEarlyAfterRound = ""; } + + config.rawConfig.tabulatorVersion = "2.0.2"; } static class ConfigVersionIsNewerThanAppVersionException extends Exception {} diff --git a/src/main/java/network/brightspots/rcv/GuiConfigController.java b/src/main/java/network/brightspots/rcv/GuiConfigController.java index c3998773d..0aff7d4a4 100644 --- a/src/main/java/network/brightspots/rcv/GuiConfigController.java +++ b/src/main/java/network/brightspots/rcv/GuiConfigController.java @@ -183,12 +183,8 @@ public class GuiConfigController implements Initializable { @FXML private TableColumn tableColumnCvrOvervoteLabel; @FXML - private TableColumn tableColumnCvrSkippedRankLabel; - @FXML private TableColumn tableColumnCvrUndeclaredWriteInLabel; @FXML - private TableColumn tableColumnCvrTreatBlankAsUndeclaredWriteIn; - @FXML private ChoiceBox choiceCvrProvider; @FXML private Button buttonAddCvrFile; @@ -213,12 +209,8 @@ public class GuiConfigController implements Initializable { @FXML private TextField textFieldCvrOvervoteLabel; @FXML - private TextField textFieldCvrSkippedRankLabel; - @FXML private TextField textFieldCvrUndeclaredWriteInLabel; @FXML - private CheckBox checkBoxCvrTreatBlankAsUndeclaredWriteIn; - @FXML private TableView tableViewCandidates; @FXML private TableColumn tableColumnCandidateName; @@ -848,9 +840,7 @@ public void buttonAddCvrFileClicked() { String provider = getProviderChoice(choiceCvrProvider).getInternalLabel(); String contestId = getTextOrEmptyString(textFieldCvrContestId); String overvoteLabel = getTextOrEmptyString(textFieldCvrOvervoteLabel); - String skippedRankLabel = getTextOrEmptyString(textFieldCvrSkippedRankLabel); String undeclaredWriteInLabel = getTextOrEmptyString(textFieldCvrUndeclaredWriteInLabel); - boolean treatBlankAsUndeclaredWriteIn = checkBoxCvrTreatBlankAsUndeclaredWriteIn.isSelected(); cvrPaths.forEach(filePath -> { CvrSource cvrSource = @@ -865,9 +855,7 @@ public void buttonAddCvrFileClicked() { provider, contestId, overvoteLabel, - skippedRankLabel, - undeclaredWriteInLabel, - treatBlankAsUndeclaredWriteIn + undeclaredWriteInLabel ); Set validationErrors = ContestConfig.performBasicCvrSourceValidation(cvrSource); @@ -893,7 +881,6 @@ private void clearBasicCvrValidationHighlighting() { textFieldCvrPrecinctCol, textFieldCvrOvervoteDelimiter, textFieldCvrOvervoteLabel, - textFieldCvrSkippedRankLabel, textFieldCvrUndeclaredWriteInLabel ); controlsToClear.forEach(GuiConfigController::clearErrorStyling); @@ -941,10 +928,6 @@ private void highlightInputsFailingBasicCvrValidation(Set valid addErrorStyling(textFieldCvrOvervoteLabel); } - if (validationErrors.contains(ValidationError.CVR_SKIPPED_RANK_LABEL_INVALID)) { - addErrorStyling(textFieldCvrSkippedRankLabel); - } - if (validationErrors.contains(ValidationError.CVR_UWI_LABEL_INVALID)) { addErrorStyling(textFieldCvrUndeclaredWriteInLabel); } @@ -985,12 +968,8 @@ private void clearAndDisableCvrFilesTabFields() { textFieldCvrContestId.setDisable(true); textFieldCvrOvervoteLabel.clear(); textFieldCvrOvervoteLabel.setDisable(true); - textFieldCvrSkippedRankLabel.clear(); - textFieldCvrSkippedRankLabel.setDisable(true); textFieldCvrUndeclaredWriteInLabel.clear(); textFieldCvrUndeclaredWriteInLabel.setDisable(true); - checkBoxCvrTreatBlankAsUndeclaredWriteIn.setSelected(false); - checkBoxCvrTreatBlankAsUndeclaredWriteIn.setDisable(true); } /** @@ -1199,8 +1178,8 @@ ConfigComparisonResult compareConfigs() { .writeValueAsString(configFromFile); if (currentConfigString.equals(savedConfigString)) { comparisonResult = ConfigComparisonResult.SAME; - } else if (configFromFile.tabulatorVersion.equals(ContestConfig.AUTOMATED_TEST_VERSION)) { - comparisonResult = ConfigComparisonResult.DIFFERENT_BUT_VERSION_IS_TEST; + } else if (ContestConfig.isConfigFileInTestDir(selectedFile.getAbsolutePath())) { + comparisonResult = ConfigComparisonResult.DIFFERENT_BUT_FILE_IN_TEST_DIR; } // Otherwise, comparisonResult should remain ConfigComparisonResult.DIFFERENT } @@ -1281,7 +1260,7 @@ private Pair commitConfigToFileAndGetFilePath() { // Pop up either a two-button or three-button alert Alert alert; - if (comparisonResult == ConfigComparisonResult.DIFFERENT_BUT_VERSION_IS_TEST) { + if (comparisonResult == ConfigComparisonResult.DIFFERENT_BUT_FILE_IN_TEST_DIR) { alert = new Alert( AlertType.WARNING, @@ -1359,6 +1338,8 @@ public LocalDate fromString(String string) { choiceCvrProvider.setOnAction(event -> { clearAndDisableCvrFilesTabFields(); textFieldCvrUndeclaredWriteInLabel.setDisable(false); + textFieldCvrUndeclaredWriteInLabel.setText(ContestConfig.SUGGESTED_UWI_LABEL); + switch (getProviderChoice(choiceCvrProvider)) { case ESS -> { buttonAddCvrFile.setDisable(false); @@ -1379,11 +1360,6 @@ public LocalDate fromString(String string) { textFieldCvrOvervoteDelimiter.setDisable(false); textFieldCvrOvervoteLabel.setDisable(false); textFieldCvrOvervoteLabel.setText(ContestConfig.SUGGESTED_OVERVOTE_LABEL); - textFieldCvrSkippedRankLabel.setDisable(false); - textFieldCvrSkippedRankLabel.setText(ContestConfig.SUGGESTED_SKIPPED_RANK_LABEL); - checkBoxCvrTreatBlankAsUndeclaredWriteIn.setDisable(false); - checkBoxCvrTreatBlankAsUndeclaredWriteIn - .setSelected(ContestConfig.SUGGESTED_TREAT_BLANK_AS_UNDECLARED_WRITE_IN); } case CSV -> { buttonAddCvrFile.setDisable(false); @@ -1443,11 +1419,8 @@ public LocalDate fromString(String string) { new EditableColumnString(tableColumnCvrOvervoteDelimiter, "overvoteDelimiter"), new EditableColumnString(tableColumnCvrContestId, "contestId"), new EditableColumnString(tableColumnCvrOvervoteLabel, "overvoteLabel"), - new EditableColumnString(tableColumnCvrSkippedRankLabel, "skippedRankLabel"), new EditableColumnString(tableColumnCvrUndeclaredWriteInLabel, "undeclaredWriteInLabel"), - new EditableColumnBoolean(tableColumnCvrTreatBlankAsUndeclaredWriteIn, - "treatBlankAsUndeclaredWriteIn"), }; setUpEditableTableStrings(cvrStringColumnsAndProperties); @@ -1460,12 +1433,8 @@ public LocalDate fromString(String string) { ); tableColumnCvrContestId.setCellValueFactory(new PropertyValueFactory<>("contestId")); tableColumnCvrOvervoteLabel.setCellValueFactory(new PropertyValueFactory<>("overvoteLabel")); - tableColumnCvrSkippedRankLabel - .setCellValueFactory(new PropertyValueFactory<>("skippedRankLabel")); tableColumnCvrUndeclaredWriteInLabel .setCellValueFactory(new PropertyValueFactory<>("undeclaredWriteInLabel")); - tableColumnCvrTreatBlankAsUndeclaredWriteIn - .setCellValueFactory(new PropertyValueFactory<>("treatBlankAsUndeclaredWriteIn")); tableViewCvrFiles.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); tableViewCvrFiles.setEditable(true); tableViewCvrFiles.getColumns().add(0, NumberTableCellFactory.createNumberColumn("#", 1)); @@ -1642,7 +1611,6 @@ private boolean isAnyPropertyValueFunctionMissing(EditableColumn[] columnsAndPro private void loadConfig(ContestConfig config) throws ConfigVersionIsNewerThanAppVersionException { clearConfig(); RawContestConfig rawConfig = config.getRawConfig(); - ContestConfigMigration.migrateConfigVersion(config); OutputSettings outputSettings = rawConfig.outputSettings; textFieldContestName.setText(outputSettings.contestName); textFieldOutputDirectory.setText(config.getOutputDirectoryRaw()); @@ -1767,8 +1735,6 @@ private RawContestConfig createRawContestConfig() { source.setContestId(source.getContestId() != null ? source.getContestId().trim() : ""); source.setOvervoteLabel( source.getOvervoteLabel() != null ? source.getOvervoteLabel().trim() : ""); - source.setSkippedRankLabel( - source.getSkippedRankLabel() != null ? source.getSkippedRankLabel().trim() : ""); source.setUndeclaredWriteInLabel( source.getUndeclaredWriteInLabel() != null ? source.getUndeclaredWriteInLabel().trim() : ""); @@ -1820,7 +1786,7 @@ public enum ConfigComparisonResult { GUI_IS_EMPTY, SAME, DIFFERENT, - DIFFERENT_BUT_VERSION_IS_TEST, + DIFFERENT_BUT_FILE_IN_TEST_DIR, } /** @@ -1869,53 +1835,8 @@ protected Task createTask() { new Task<>() { @Override protected Void call() { - Logger.info("Auto-loading candidates from CVR files..."); - boolean cvrsSpecified = true; - if (sources.isEmpty()) { - Logger.warning("No CVR files specified!"); - cvrsSpecified = false; - } - if (cvrsSpecified) { - // Gather unloaded names from each of the sources and place into the HashSet - HashSet unloadedCandidates = new HashSet<>(); - for (CvrSource source : sources) { - Provider provider = ContestConfig.getProvider(source); - try { - List castVoteRecords = new ArrayList<>(); - BaseCvrReader reader = provider.constructReader(config, source); - Set unknownCandidates = - reader.gatherUnknownCandidates(castVoteRecords); - unloadedCandidates.addAll(unknownCandidates); - } catch (ContestConfig.UnrecognizedProviderException e) { - Logger.severe( - "Unrecognized provider \"%s\" in source file \"%s\": %s", - source.getProvider(), source.getFilePath(), e.getMessage()); - } catch (CastVoteRecord.CvrParseException | IOException e) { - Logger.severe( - "Failed to read source file \"%s\": ", - source.getFilePath(), e.getMessage()); - } - } - - // Validate each name and add to the table of candidates - int successCount = 0; - for (Candidate candidate : unloadedCandidates) { - Set validationErrors = - ContestConfig.performBasicCandidateValidation(candidate); - if (validationErrors.isEmpty()) { - tableViewCandidates.getItems().add(candidate); - successCount++; - } else { - String errors = validationErrors.stream() - .map(x -> x.toString()) - .collect(Collectors.joining(", ")); - Logger.warning("Autoloaded candidate %s but did not pass validation." - + " (%s)", candidate, errors); - } - } - - Logger.info("Auto-loaded %d candidates.", successCount); - } + Set newCandidates = gatherAutoloadedCandidates(config, sources); + tableViewCandidates.getItems().addAll(newCandidates); return null; } }; @@ -1927,6 +1848,57 @@ protected Void call() { } } + /** + * Reads all CVR sources and returns candidates found in the CVRs that are not already listed + * in the config. Candidates that fail basic validation are logged and excluded from the result. + */ + static Set gatherAutoloadedCandidates( + ContestConfig config, List sources) { + Logger.info("Auto-loading candidates from CVR files..."); + if (sources.isEmpty()) { + Logger.warning("No CVR files specified!"); + return Set.of(); + } + + // Gather unknown candidates across all sources into a single set + HashSet unloadedCandidates = new HashSet<>(); + for (CvrSource source : sources) { + Provider provider = ContestConfig.getProvider(source); + try { + List castVoteRecords = new ArrayList<>(); + BaseCvrReader reader = provider.constructReader(config, source); + unloadedCandidates.addAll(reader.gatherUnknownCandidates(castVoteRecords)); + } catch (ContestConfig.UnrecognizedProviderException e) { + Logger.severe( + "Unrecognized provider \"%s\" in source file \"%s\": %s", + source.getProvider(), source.getFilePath(), e.getMessage()); + } catch (CastVoteRecord.CvrParseException | IOException e) { + Logger.severe( + "Failed to read source file \"%s\": ", + source.getFilePath(), e.getMessage()); + } + } + + // Filter out candidates that fail basic validation + HashSet validCandidates = new HashSet<>(); + for (Candidate candidate : unloadedCandidates) { + Set validationErrors = + ContestConfig.performBasicCandidateValidation(candidate); + if (validationErrors.isEmpty()) { + validCandidates.add(candidate); + } else { + String errors = validationErrors.stream() + .map(ValidationError::toString) + .collect(Collectors.joining(", ")); + Logger.warning( + "Autoloaded candidate %s but did not pass validation. (%s)", candidate, errors); + } + } + + Logger.info("Auto-loaded %d candidates.", validCandidates.size()); + return validCandidates; + } + private static class ValidatorService extends GenericService { private final ContestConfig contestConfig; diff --git a/src/main/java/network/brightspots/rcv/GuiTabulateController.java b/src/main/java/network/brightspots/rcv/GuiTabulateController.java index 363f46a54..dd99013a1 100644 --- a/src/main/java/network/brightspots/rcv/GuiTabulateController.java +++ b/src/main/java/network/brightspots/rcv/GuiTabulateController.java @@ -237,7 +237,9 @@ private void watchParseCvrServiceProgress(Service service) { perSourceCvrCountTable.setVisible(true); } - data.discard(); + if (data != null) { + data.discard(); + } lastLoadedCvrData = data; }; @@ -372,7 +374,7 @@ private void initializeSaveButtonStatuses() { saveButton.setText("Save*"); tempSaveButton.setVisible(false); break; - case DIFFERENT_BUT_VERSION_IS_TEST: + case DIFFERENT_BUT_FILE_IN_TEST_DIR: saveButton.setText("Save*"); tempSaveButton.setVisible(true); break; diff --git a/src/main/java/network/brightspots/rcv/Main.java b/src/main/java/network/brightspots/rcv/Main.java index 8a2a05f8f..c7f6e2233 100644 --- a/src/main/java/network/brightspots/rcv/Main.java +++ b/src/main/java/network/brightspots/rcv/Main.java @@ -37,7 +37,7 @@ public class Main extends GuiApplication { // TODO Sync version number with release.yml and build.gradle: // github.com/BrightSpots/rcv/issues/662 - public static final String APP_VERSION = "2.0.2"; + public static final String APP_VERSION = "2.1.0"; /** * Main entry point to RCTab. diff --git a/src/main/java/network/brightspots/rcv/RawContestConfig.java b/src/main/java/network/brightspots/rcv/RawContestConfig.java index e1a027779..8bbb33e6b 100644 --- a/src/main/java/network/brightspots/rcv/RawContestConfig.java +++ b/src/main/java/network/brightspots/rcv/RawContestConfig.java @@ -19,8 +19,10 @@ import static network.brightspots.rcv.Utils.isNullOrBlank; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonSetter; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; @@ -77,11 +79,12 @@ public static class CvrSource { private final SimpleStringProperty overvoteDelimiter = new SimpleStringProperty(); private final SimpleStringProperty provider = new SimpleStringProperty(); private final SimpleStringProperty overvoteLabel = new SimpleStringProperty(); - private final SimpleStringProperty skippedRankLabel = new SimpleStringProperty(); private final SimpleStringProperty undeclaredWriteInLabel = new SimpleStringProperty(); - private final SimpleBooleanProperty treatBlankAsUndeclaredWriteIn = new SimpleBooleanProperty(); + // Deprecated fields + private String skippedRankLabel; + private boolean treatBlankAsUndeclaredWriteIn; private String undervoteLabel; CvrSource() {} @@ -97,9 +100,7 @@ public static class CvrSource { String provider, String contestId, String overvoteLabel, - String skippedRankLabel, - String undeclaredWriteInLabel, - boolean treatBlankAsUndeclaredWriteIn) { + String undeclaredWriteInLabel) { this.filePath.set(filePath); this.firstVoteColumnIndex.set(firstVoteColumnIndex); this.firstVoteRowIndex.set(firstVoteRowIndex); @@ -110,9 +111,7 @@ public static class CvrSource { this.provider.set(provider); this.contestId.set(contestId); this.overvoteLabel.set(overvoteLabel); - this.skippedRankLabel.set(skippedRankLabel); this.undeclaredWriteInLabel.set(undeclaredWriteInLabel); - this.treatBlankAsUndeclaredWriteIn.set(treatBlankAsUndeclaredWriteIn); } public String getFilePath() { @@ -199,14 +198,6 @@ public void setOvervoteLabel(String overvoteLabel) { this.overvoteLabel.set(overvoteLabel); } - public String getSkippedRankLabel() { - return skippedRankLabel.get(); - } - - public void setSkippedRankLabel(String skippedRankLabel) { - this.skippedRankLabel.set(skippedRankLabel); - } - public String getUndeclaredWriteInLabel() { return undeclaredWriteInLabel.get(); } @@ -215,12 +206,23 @@ public void setUndeclaredWriteInLabel(String undeclaredWriteInLabel) { this.undeclaredWriteInLabel.set(undeclaredWriteInLabel); } + /** + * The following properties are deprecated. + */ + public String getSkippedRankLabel() { + return skippedRankLabel; + } + + public void setSkippedRankLabel(String skippedRankLabel) { + this.skippedRankLabel = skippedRankLabel; + } + public boolean getTreatBlankAsUndeclaredWriteIn() { - return treatBlankAsUndeclaredWriteIn.get(); + return treatBlankAsUndeclaredWriteIn; } public void setTreatBlankAsUndeclaredWriteIn(Boolean treatBlankAsUndeclaredWriteIn) { - this.treatBlankAsUndeclaredWriteIn.set(treatBlankAsUndeclaredWriteIn); + this.treatBlankAsUndeclaredWriteIn = treatBlankAsUndeclaredWriteIn; } /** @@ -267,22 +269,9 @@ public SimpleStringProperty overvoteLabelProperty() { return overvoteLabel; } - public SimpleStringProperty skippedRankLabelProperty() { - return skippedRankLabel; - } - public SimpleStringProperty undeclaredWriteInLabelProperty() { return undeclaredWriteInLabel; } - - public SimpleBooleanProperty treatBlankAsUndeclaredWriteInProperty() { - return treatBlankAsUndeclaredWriteIn; - } - - // Deprecated fields - public String getUndervoteLabel() { - return undervoteLabel; - } } /** Contest candidate data that can be serialized and deserialized. */ @@ -325,6 +314,33 @@ public String toString() { return "Name: " + name + " Aliases: " + aliases; } + /** The key for dictionaries includes: + 1. [excluded|not] + 2. name + 3. newline-separated aliases + This is unique for each candidate because newline is the only truly-disallowed character + */ + @Override + public int hashCode() { + String nameString = getName() == null ? "" : getName(); + String aliasesString = String.join("\n", getAliases()); + return "%b%n%s%n%s".formatted(getExcluded(), nameString, aliasesString).hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Candidate other)) { + return false; + } + return this.getExcluded() == other.getExcluded() + && this.getName().equals(other.getName()) + && this.getAliases().equals(other.getAliases()); + } + + public String getName() { return name.getValue(); } @@ -424,11 +440,11 @@ public static class ContestRules { public boolean exhaustOnDuplicateCandidate; public String rulesDescription; - // These are deprecated (moved to individual CVRs), but we need to leave them in place here for + // These field have been moved to individual CVRs, but we need to leave them in place here for // the purpose of supporting automatic migration from older config versions. - public boolean treatBlankAsUndeclaredWriteIn; + // Fields that were not moved/renamed, but rather were deleted, are not included here: + // when loading CVRs with those fields, they will simply be ignored and deleted on when saved. public String overvoteLabel; - public String undervoteLabel; public String undeclaredWriteInLabel; } } diff --git a/src/main/java/network/brightspots/rcv/StreamingCvrReader.java b/src/main/java/network/brightspots/rcv/StreamingCvrReader.java index 96959e4fb..073f6f1db 100644 --- a/src/main/java/network/brightspots/rcv/StreamingCvrReader.java +++ b/src/main/java/network/brightspots/rcv/StreamingCvrReader.java @@ -21,9 +21,15 @@ import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.security.InvalidParameterException; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.regex.Pattern; import javafx.util.Pair; import javax.xml.parsers.ParserConfigurationException; @@ -31,19 +37,27 @@ import javax.xml.parsers.SAXParserFactory; import org.apache.poi.openxml4j.exceptions.OpenXML4JException; import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.openxml4j.opc.PackageRelationshipCollection; +import org.apache.poi.openxml4j.util.ZipSecureFile; import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable; import org.apache.poi.xssf.eventusermodel.XSSFReader; import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler; import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler.SheetContentsHandler; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFComment; +import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; final class StreamingCvrReader extends BaseCvrReader { + // this indicates a voter did not use this ranking + private static final String SKIPPED_RANK_STRING = "undervote"; // this indicates a missing precinct ID in output files private static final String MISSING_PRECINCT_ID = "missing_precinct_id"; // this indicates a missing batch ID in output files @@ -63,15 +77,11 @@ final class StreamingCvrReader extends BaseCvrReader { // optional delimiter for cells that contain multiple candidates private final String overvoteDelimiter; private final String overvoteLabel; - private final String skippedRankLabel; private final String undeclaredWriteInLabel; - private final boolean treatBlankAsUndeclaredWriteIn; // used for generating CVR IDs private int cvrIndex = 0; // list of currentRankings for CVR in progress private LinkedList> currentRankings; - // list of raw strings for CVR in progress - private LinkedList currentCvrData; // supplied CVR ID for CVR in progress private String currentSuppliedCvrId; // batch ID for CVR in progress @@ -82,8 +92,18 @@ final class StreamingCvrReader extends BaseCvrReader { private List cvrList; // last rankings cell observed for CVR in progress private int lastRankSeen; + // has this CVR had any blank candidate cells? + private boolean hasSeenAnyBlankCandidateCells; + // has this CVR had any non-blank candidate cells? + private boolean hasSeenAnyNonBlankCandidateCells; + // total number of rows where there were only blank candidates + private int numRowsIgnoredBecauseAllBlank; // flag indicating data issues during parsing private boolean encounteredDataErrors = false; + // map of row → set of columns that contain images in the drawing layer (both 0-based) + private Map> imageCells = new HashMap<>(); + // 0-based row index of the row currently being parsed + private int currentRowIndex; StreamingCvrReader(ContestConfig config, RawContestConfig.CvrSource source) { super(config, source); @@ -106,9 +126,7 @@ final class StreamingCvrReader extends BaseCvrReader { : null; this.overvoteDelimiter = source.getOvervoteDelimiter(); this.overvoteLabel = source.getOvervoteLabel(); - this.skippedRankLabel = source.getSkippedRankLabel(); this.undeclaredWriteInLabel = source.getUndeclaredWriteInLabel(); - this.treatBlankAsUndeclaredWriteIn = source.getTreatBlankAsUndeclaredWriteIn(); } // given Excel-style address string return the cell address as a pair of Integers @@ -154,11 +172,18 @@ public String readerName() { // occur in a ranking's cell. // param: currentRank the rank at which we stop inferring empty cells for this invocation private void handleEmptyCells(int currentRank) { + Set rowImageCols = imageCells.getOrDefault(currentRowIndex, Set.of()); for (int rank = lastRankSeen + 1; rank < currentRank; rank++) { - currentCvrData.add("empty cell"); - // add UWI ranking if required by settings - if (treatBlankAsUndeclaredWriteIn) { + int col = firstVoteColumnIndex + rank - 1; + if (rowImageCols.contains(col)) { + rowImageCols.remove(col); + if (rowImageCols.isEmpty()) { + imageCells.remove(currentRowIndex); + } currentRankings.add(new Pair<>(rank, Tabulator.UNDECLARED_WRITE_IN_OUTPUT_LABEL)); + hasSeenAnyNonBlankCandidateCells = true; + } else { + hasSeenAnyBlankCandidateCells = true; } } } @@ -167,11 +192,12 @@ private void handleEmptyCells(int currentRank) { private void beginCvr() { cvrIndex++; currentRankings = new LinkedList<>(); - currentCvrData = new LinkedList<>(); currentSuppliedCvrId = null; currentBatch = null; currentPrecinct = null; lastRankSeen = 0; + hasSeenAnyNonBlankCandidateCells = false; + hasSeenAnyBlankCandidateCells = false; } // complete construction of new CVR object @@ -179,10 +205,31 @@ private void endCvr() { // handle any empty cells which may appear at the end of this row if (!config.isMaxRankingsSetToMaximum()) { handleEmptyCells(config.getMaxRankingsAllowedWhenNotSetToMaximum() + 1); + } else { + // With no upper bound on rankings, trailing blank cells are never visited by the SAX + // parser, so we must still sweep up any write-in images that sit beyond lastRankSeen. + Set rowImageCols = imageCells.getOrDefault(currentRowIndex, Set.of()); + if (!rowImageCols.isEmpty()) { + int maxImageRank = Collections.max(rowImageCols) - firstVoteColumnIndex + 1; + if (maxImageRank > lastRankSeen) { + handleEmptyCells(maxImageRank + 1); + } + } } String computedCastVoteRecordId = String.format("%s-%d", OutputWriter.sanitizeStringForOutput(excelFileName), cvrIndex); + if (hasSeenAnyNonBlankCandidateCells && hasSeenAnyBlankCandidateCells) { + Logger.severe("Blank cells are not allowed unless the entire row is blank (CVR %s)", + computedCastVoteRecordId); + encounteredDataErrors = true; + } else if (!hasSeenAnyNonBlankCandidateCells) { + Logger.auditable( + "Skipping CVR for irrelevant contest: %s", computedCastVoteRecordId); + numRowsIgnoredBecauseAllBlank++; + return; + } + // add precinct ID if needed if (precinctColumnIndex != null) { if (currentPrecinct == null) { @@ -228,7 +275,6 @@ private void endCvr() { // handle CVR cell data callback private void cvrCell(int col, String cellData) { - currentCvrData.add(cellData); if (precinctColumnIndex != null && col == precinctColumnIndex) { currentPrecinct = cellData; } else if (batchColumnIndex != null && col == batchColumnIndex) { @@ -259,16 +305,19 @@ private void cvrCell(int col, String cellData) { for (String candidate : candidates) { candidate = candidate.trim(); - if (candidates.length > 1 && (candidate.isBlank() || candidate.equals(skippedRankLabel))) { + hasSeenAnyNonBlankCandidateCells |= !candidate.isBlank(); + hasSeenAnyBlankCandidateCells |= candidate.isBlank(); + if (candidates.length > 1 && candidate.isBlank()) { Logger.severe( "If a cell contains multiple candidates split by the overvote delimiter, " + "it's not valid for any of them to be blank or an explicit skipped ranking."); encounteredDataErrors = true; - } else if (!candidate.equals(skippedRankLabel)) { - // map overvotes to our internal overvote string - if (candidate.equals(overvoteLabel)) { + } else if (!candidate.isBlank()) { + if (candidate.equals(SKIPPED_RANK_STRING)) { + continue; + } else if (candidate.equals(overvoteLabel)) { candidate = Tabulator.EXPLICIT_OVERVOTE_LABEL; - } else if (candidate.equals(undeclaredWriteInLabel)) { + } else if (isUndeclaredWriteIn(candidate)) { candidate = Tabulator.UNDECLARED_WRITE_IN_OUTPUT_LABEL; } Pair ranking = new Pair<>(currentRank, candidate); @@ -280,6 +329,10 @@ private void cvrCell(int col, String cellData) { } } + boolean isUndeclaredWriteIn(String candidateName) { + return candidateName.equals(undeclaredWriteInLabel); + } + @Override void readCastVoteRecords(List castVoteRecords) throws CastVoteRecord.CvrParseException, IOException { @@ -291,6 +344,7 @@ void readCastVoteRecords(List castVoteRecords) Logger.info( "ES&S cast vote record files must be Microsoft Excel Workbook " + "format.\nStrict Open XML and Open Office are not supported."); + Logger.info("Actual error: " + e.getMessage()); throw new CastVoteRecord.CvrParseException(); } catch (CvrDataFormatException exception) { Logger.severe("Data format error while parsing source file: %s", cvrPath); @@ -311,6 +365,8 @@ private void parseCvrFileInternal(List castVoteRecords) cvrList = castVoteRecords; + // handle at least 10 million write-in images + ZipSecureFile.setMaxFileCount(10_000_000); // open the zip package OPCPackage pkg = OPCPackage.open(cvrPath); // pull out strings @@ -319,11 +375,14 @@ private void parseCvrFileInternal(List castVoteRecords) XSSFReader xssfReader = new XSSFReader(pkg); // styles data is used for creating ContentHandler StylesTable styles = xssfReader.getStylesTable(); + // pre-scan drawing XML to map image positions to cells before streaming + imageCells = buildImageCellSet(xssfReader, pkg); // object for handling Excel parsing callbacks SheetContentsHandler sheetContentsHandler = new SheetContentsHandler() { @Override public void startRow(int i) { + currentRowIndex = i; if (i >= firstVoteRowIndex) { beginCvr(); } @@ -366,13 +425,162 @@ public void headerFooter(String s, boolean b, String s1) { xmlReader.setContentHandler(handler); // parse xmlReader.parse(new InputSource(xssfReader.getSheetsData().next())); + + for (Map.Entry> entry : imageCells.entrySet()) { + for (int col : entry.getValue()) { + Logger.severe("Image at row %d, col %d was never visited during parsing", + entry.getKey(), col); + encounteredDataErrors = true; + } + } + // close zip file without saving pkg.revert(); + if (numRowsIgnoredBecauseAllBlank > 0) { + Logger.warning("Ignored %d rows because the ES&S CVR format indicates the " + + "configured contest did not appear on those ballots.", + numRowsIgnoredBecauseAllBlank); + } + if (encounteredDataErrors) { throw new CvrDataFormatException(); } } + // Pre-scan the drawing XML for the first sheet to find cells containing images. + // Images in xlsx are floating objects in a separate drawing part, not embedded in cell data, + // so they never appear in XSSFSheetXMLHandler callbacks. The anchor's "from" element gives the + // 0-based (col, row) of the cell the image is anchored to. + private Map> buildImageCellSet(XSSFReader xssfReader, OPCPackage pkg) + throws OpenXML4JException, IOException, SAXException, ParserConfigurationException { + Map> cells = new HashMap<>(); + XSSFReader.SheetIterator sheetIter = (XSSFReader.SheetIterator) xssfReader.getSheetsData(); + if (!sheetIter.hasNext()) { + return cells; + } + // Advance to the first sheet so getSheetPart() is populated, then close the stream. + try (InputStream ignored = sheetIter.next()) { + // nothing to read; we only need the sheet's package relationships + } + PackagePart sheetPart = sheetIter.getSheetPart(); + PackageRelationshipCollection drawingRels = sheetPart.getRelationshipsByType( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"); + + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setNamespaceAware(true); + int lastVoteColExclusive = config.isMaxRankingsSetToMaximum() + ? Integer.MAX_VALUE + : firstVoteColumnIndex + config.getMaxRankingsAllowedWhenNotSetToMaximum(); + for (PackageRelationship rel : drawingRels) { + PackagePart drawingPart = sheetPart.getRelatedPart(rel); + DrawingImageHandler handler = new DrawingImageHandler( + cells, firstVoteRowIndex, firstVoteColumnIndex, lastVoteColExclusive); + try (InputStream is = drawingPart.getInputStream()) { + factory.newSAXParser().parse(is, handler); + } + } + return cells; + } + + // SAX handler that parses a spreadsheet drawing XML and records the anchor cell of every picture. + private static class DrawingImageHandler extends DefaultHandler { + // namespace for spreadsheet drawing elements (xdr:*) + private static final String XDR_NS = + "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"; + + private final Map> imageCells; + private final int firstVoteRowIndex; + private final int firstVoteColumnIndex; + private final int lastVoteColExclusive; + private boolean inFrom = false; + private boolean inCol = false; + private boolean inRow = false; + private boolean hasPic = false; + private int fromCol = -1; + private int fromRow = -1; + private final StringBuilder text = new StringBuilder(); + + DrawingImageHandler(Map> imageCells, + int firstVoteRowIndex, int firstVoteColumnIndex, int lastVoteColExclusive) { + this.imageCells = imageCells; + this.firstVoteRowIndex = firstVoteRowIndex; + this.firstVoteColumnIndex = firstVoteColumnIndex; + this.lastVoteColExclusive = lastVoteColExclusive; + } + + @Override + public void startElement(String uri, String localName, String qualifiedName, Attributes attrs) { + text.setLength(0); + if (!XDR_NS.equals(uri)) { + return; + } + switch (localName) { + case "twoCellAnchor", "oneCellAnchor" -> { + fromCol = -1; + fromRow = -1; + hasPic = false; + } + case "from" -> { + inFrom = true; + } + case "col" -> { + if (inFrom) { + inCol = true; + } + } + case "row" -> { + if (inFrom) { + inRow = true; + } + } + case "pic" -> { + hasPic = true; + } + default -> {} + } + } + + @Override + public void endElement(String uri, String localName, String qualifiedName) { + if (XDR_NS.equals(uri)) { + switch (localName) { + case "from" -> { + inFrom = false; + } + case "col" -> { + if (inCol) { + fromCol = Integer.parseInt(text.toString().trim()); + inCol = false; + } + } + case "row" -> { + if (inRow) { + fromRow = Integer.parseInt(text.toString().trim()); + inRow = false; + } + } + case "twoCellAnchor", "oneCellAnchor" -> { + if (hasPic && fromRow >= firstVoteRowIndex + && fromCol >= firstVoteColumnIndex && fromCol < lastVoteColExclusive) { + boolean added = + imageCells.computeIfAbsent(fromRow, k -> new HashSet<>()).add(fromCol); + if (!added) { + Logger.severe("Multiple images found in cell at row %d, col %d", fromRow, fromCol); + } + } + } + default -> {} + } + } + text.setLength(0); + } + + @Override + public void characters(char[] ch, int start, int length) { + text.append(ch, start, length); + } + } + static class CvrDataFormatException extends Exception {} } diff --git a/src/main/java/network/brightspots/rcv/TabulatorSession.java b/src/main/java/network/brightspots/rcv/TabulatorSession.java index 0efc3e1bf..1e6b3f182 100644 --- a/src/main/java/network/brightspots/rcv/TabulatorSession.java +++ b/src/main/java/network/brightspots/rcv/TabulatorSession.java @@ -62,6 +62,7 @@ class TabulatorSession { // If there are multiple runs in the same minute, resolve collisions // with a dash and an increment. ContestConfig config = ContestConfig.loadContestConfig(configPath); + int count = 1; while (new File(config.getOutputDirectory(currTimestampString)).exists()) { currTimestampString = baseTimestampString + "-" + count; @@ -75,21 +76,20 @@ class TabulatorSession { // here also private static void checkConfigVersionMatchesApp(ContestConfig config) { String version = config.getRawConfig().tabulatorVersion; - if (!version.equals(ContestConfig.AUTOMATED_TEST_VERSION)) { - // Below already logs a severe message, so no need to check and add another one - boolean isConfigVersionNewerThanAppVersion = - ContestConfigMigration.isConfigVersionNewerThanAppVersion(version); - if (!isConfigVersionNewerThanAppVersion - && ContestConfigMigration.isConfigVersionOlderThanAppVersion(version)) { - Logger.severe( - "Can't use a config with older version %s in newer version %s of the app! To " - + "automatically migrate the config to the newer version, load it in the graphical " - + "version of the app (i.e. don't use the --cli flag when starting the tabulator).", - version, Main.APP_VERSION); - } - // No need to throw errors for these, because they'll be caught by validateTabulatorVersion() - // during validation + + // Below already logs a severe message, so no need to check and add another one + boolean isConfigVersionNewerThanAppVersion = + ContestConfigMigration.isConfigVersionNewerThanAppVersion(version); + if (!isConfigVersionNewerThanAppVersion + && ContestConfigMigration.isConfigVersionOlderThanAppVersion(version)) { + Logger.severe( + "Can't use a config with older version %s in newer version %s of the app! To " + + "automatically migrate the config to the newer version, load it in the graphical " + + "version of the app (i.e. don't use the --cli flag when starting the tabulator).", + version, Main.APP_VERSION); } + // No need to throw errors for these, because they'll be caught by validateTabulatorVersion() + // during validation } // Visible for testing diff --git a/src/main/resources/network/brightspots/rcv/GuiConfigLayout.fxml b/src/main/resources/network/brightspots/rcv/GuiConfigLayout.fxml index 0e7dacf22..8a1cba9ca 100644 --- a/src/main/resources/network/brightspots/rcv/GuiConfigLayout.fxml +++ b/src/main/resources/network/brightspots/rcv/GuiConfigLayout.fxml @@ -144,14 +144,6 @@ - - - - - - - - - - - - - - - @@ -293,14 +274,9 @@ - - diff --git a/src/main/resources/network/brightspots/rcv/hints_cvr_files.txt b/src/main/resources/network/brightspots/rcv/hints_cvr_files.txt index 92b29125f..ffb37327d 100644 --- a/src/main/resources/network/brightspots/rcv/hints_cvr_files.txt +++ b/src/main/resources/network/brightspots/rcv/hints_cvr_files.txt @@ -32,8 +32,4 @@ Overvote Delimiter (optional, but must be blank if "Overvote Label" is provided) Overvote Label (optional): Some CDF and ES&S CVRs use a particular word/phrase to indicate an overvote. -Undervote Label (optional): Some ES&S CVRs use a particular word/phrase to indicate an undervote. - -Undeclared Write-in Label (optional): Some CVRs use a particular word/phrase to indicate an undeclared write-in. - -Treat Blank as Undeclared Write-in (optional): When checked, the tabulator will interpret blank cells in this ES&S CVR as votes for undeclared write-ins. +Undeclared Write-in Label (optional): Some CVRs use a particular word/phrase to indicate an undeclared write-in. \ No newline at end of file diff --git a/src/test/java/network/brightspots/rcv/Common.java b/src/test/java/network/brightspots/rcv/Common.java new file mode 100644 index 000000000..0b39c6e6d --- /dev/null +++ b/src/test/java/network/brightspots/rcv/Common.java @@ -0,0 +1,80 @@ +/* + * RCTab + * Copyright (c) 2017-2026 Bright Spots Developers. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/* + * Purpose: Shared utilities for tabulator test classes. + * Design: Provides helpers for generating and managing test config files. + * Conditions: During automated testing. + * Version history: see https://github.com/BrightSpots/rcv. + */ + +package network.brightspots.rcv; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.function.UnaryOperator; + +class Common { + + static final String TEST_ASSET_FOLDER = + "src/test/resources/network/brightspots/rcv/test_data"; + + @FunctionalInterface + interface ConfigConsumer { + void accept(Path configPath) throws Exception; + } + + /** + * Calls the consumer on a config file for each supported provider type. + * Each returned config file uses a different provider, but the data in each CVR is the same, + * and the results of tabulation (and nearly every other operation) should be equivalent. + * Under the hood, this generates temporary config files and manages their lifecycle. + */ + static void runForEachProvider(ConfigConsumer consumer) throws Exception { + runForEachProvider(consumer, UnaryOperator.identity()); + } + + /** + * Like {@link #runForEachProvider(ConfigConsumer)}, but applies {@code configModifier} to the + * merged config JSON before writing it to disk. Use this to adjust the config for tests that + * need a non-default starting state (e.g. removing all candidates to test autoload). + */ + static void runForEachProvider( + ConfigConsumer consumer, UnaryOperator configModifier) throws Exception { + final String[] stems = {"ess", "cdf"}; + + Path sharedDir = Paths.get(System.getProperty("user.dir"), + TEST_ASSET_FOLDER, "_shared", "same_data_different_formats"); + + ObjectMapper mapper = new ObjectMapper(); + JsonNode baseConfig = mapper.readTree(sharedDir.resolve("base_config.json").toFile()); + + for (String stem : stems) { + File stemConfigFile = sharedDir.resolve(stem + "_config.json").toFile(); + JsonNode stemSources = mapper.readTree(stemConfigFile); + + ObjectNode mergedConfig = baseConfig.deepCopy(); + mergedConfig.set("cvrFileSources", stemSources); + mergedConfig = configModifier.apply(mergedConfig); + + Path tempConfig = sharedDir.resolve(stem + "_combined_config.json"); + mapper.writeValue(tempConfig.toFile(), mergedConfig); + try { + consumer.accept(tempConfig); + } finally { + Files.deleteIfExists(tempConfig); + } + } + } +} diff --git a/src/test/java/network/brightspots/rcv/ContestConfigTests.java b/src/test/java/network/brightspots/rcv/ContestConfigTests.java index dd12c64d1..378b1072c 100644 --- a/src/test/java/network/brightspots/rcv/ContestConfigTests.java +++ b/src/test/java/network/brightspots/rcv/ContestConfigTests.java @@ -111,4 +111,38 @@ void testNumDecimalsInString() { getPercentageFromStringWithAccurateSigFigs(".9999")); } + + @Test + @DisplayName("test isConfigFileInTestDir") + void testIsConfigFileInTestDir() { + // Valid cases + assertTrue(ContestConfig.isConfigFileInTestDir( + "brightspots/rcv/test_data/foo/foo_config.json")); + + assertTrue(ContestConfig.isConfigFileInTestDir( + "brightspots\\rcv\\test_data\\bar\\bar_config.json")); + + assertTrue(ContestConfig.isConfigFileInTestDir( + "/path/to/brightspots/rcv/test_data/foo/foo_config.json")); + + // Invalid: filename doesn't match directory + assertFalse(ContestConfig.isConfigFileInTestDir( + "/path/to/brightspots/rcv/test_data/foo/bar_config.json")); + + // Invalid: wrong extension + assertFalse(ContestConfig.isConfigFileInTestDir( + "brightspots/rcv/test_data/foo/foo_config.txt")); + + // Invalid: missing directory name in filename + assertFalse(ContestConfig.isConfigFileInTestDir( + "brightspots/rcv/test_data/foo/_config.json")); + + // Invalid: extra suffix + assertFalse(ContestConfig.isConfigFileInTestDir( + "brightspots/rcv/test_data/foo/foo_config.json.bak")); + + // Invalid: wrong path prefix + assertFalse(ContestConfig.isConfigFileInTestDir( + "src/test/resources/configs/test_config.json")); + } } diff --git a/src/test/java/network/brightspots/rcv/GuiFunctionalityTests.java b/src/test/java/network/brightspots/rcv/GuiFunctionalityTests.java new file mode 100644 index 000000000..78b19d972 --- /dev/null +++ b/src/test/java/network/brightspots/rcv/GuiFunctionalityTests.java @@ -0,0 +1,85 @@ +/* + * RCTab + * Copyright (c) 2017-2023 Bright Spots Developers. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/* + * Purpose: These tests check configuration files. + * Design: Unit tests, and other tests that don't run a tabulation. + * Conditions: During automated testing. + * Version history: see https://github.com/BrightSpots/rcv. + */ + +package network.brightspots.rcv; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Set; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class GuiFunctionalityTests { + @BeforeAll + static void setup() { + Logger.setup(); + } + + @Test + @DisplayName("test autoload when all candidates are present in config") + void testAutoloadWithCompleteConfig() throws Exception { + Common.runForEachProvider(configPath -> { + String configPathStr = configPath.toString(); + ContestConfig config = ContestConfig.loadContestConfig(configPathStr); + assertTrue( + GuiConfigController.gatherAutoloadedCandidates( + config, config.getRawConfig().cvrFileSources).isEmpty(), + "Expected no unknown candidates in " + configPath.getFileName()); + }); + } + + @Test + @DisplayName("test autoload when one candidate is missing") + void testAutoloadWithOneMissingCandidate() throws Exception { + Common.runForEachProvider( + configPath -> { + String configPathStr = configPath.toString(); + ContestConfig config = ContestConfig.loadContestConfig(configPathStr); + Set unloaded = GuiConfigController.gatherAutoloadedCandidates( + config, config.getRawConfig().cvrFileSources); + assertEquals( + Set.of(new RawContestConfig.Candidate("Candidate A Name")), + unloaded, + "Expected exactly the removed candidate to be autoloaded in " + + configPath.getFileName()); + }, + config -> { + config.withArray("candidates").remove(0); + return config; + }); + } + + @Test + @DisplayName("test autoload when candidates list is empty") + void testAutoloadWithEmptyConfig() throws Exception { + Common.runForEachProvider( + configPath -> { + String configPathStr = configPath.toString(); + ContestConfig config = ContestConfig.loadContestConfig(configPathStr); + assertSame(4, + GuiConfigController.gatherAutoloadedCandidates( + config, config.getRawConfig().cvrFileSources).size(), + "Expected candidates to be autoloaded in " + configPath.getFileName()); + }, + config -> { + config.set("candidates", config.arrayNode()); + return config; + }); + } +} diff --git a/src/test/java/network/brightspots/rcv/TabulatorTests.java b/src/test/java/network/brightspots/rcv/TabulatorTests.java index 5144cc304..6230ab4f0 100644 --- a/src/test/java/network/brightspots/rcv/TabulatorTests.java +++ b/src/test/java/network/brightspots/rcv/TabulatorTests.java @@ -36,6 +36,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -50,8 +51,7 @@ class TabulatorTests { // folder where we store test inputs - private static final String TEST_ASSET_FOLDER = - "src/test/resources/network/brightspots/rcv/test_data"; + private static final String TEST_ASSET_FOLDER = Common.TEST_ASSET_FOLDER; // limit log output to avoid spam private static final Integer MAX_LOG_ERRORS = 10; @@ -491,6 +491,51 @@ static void setup() { SecurityConfig.setAllowUsersDirectorySavingForUnitTests(true); } + @Test + @DisplayName("Test CVRs with same data but different formats produce same results") + void testSameDataDifferentFormats() throws Exception { + List detailedJsonPaths = new ArrayList<>(); + List detailedCsvPaths = new ArrayList<>(); + List sessions = new ArrayList<>(); + + Common.runForEachProvider(configPath -> { + String configPathStr = configPath.toString(); + TabulatorSession session = new TabulatorSession(configPathStr); + List exceptions = session.tabulate("Automated test"); + assertTrue(exceptions.isEmpty(), + "Tabulation failed for \"" + configPath.getFileName() + "\": " + exceptions); + sessions.add(session); + + String timestampString = session.getTimestampString(); + ContestConfig config = ContestConfig.loadContestConfig(configPathStr); + assertNotNull(config); + String resultsDir = config.getOutputDirectory(timestampString); + + detailedJsonPaths.add(new OutputFileIdentifiers(OutputType.DETAILED_JSON) + .getPath(resultsDir, timestampString, null)); + detailedCsvPaths.add(new OutputFileIdentifiers(OutputType.DETAILED_CSV) + .getPath(resultsDir, timestampString, null)); + }); + + // Verify all outputs match the first stem's outputs + String firstJson = detailedJsonPaths.getFirst().toString(); + String firstCsv = detailedCsvPaths.getFirst().toString(); + for (int i = 1; i < detailedJsonPaths.size(); i++) { + String thisJson = detailedJsonPaths.get(i).toString(); + String thisCsv = detailedCsvPaths.get(i).toString(); + assertTrue( + fileCompare(firstJson, thisJson), + "detailed_report.json differs between \"" + firstJson + "\" and \"" + thisJson + "\""); + assertTrue( + fileCompare(firstCsv, thisCsv), + "detailed_report.csv differs between \"" + firstCsv + "\" and \"" + thisCsv + "\""); + } + + for (TabulatorSession session : sessions) { + cleanOutputFolder(session); + } + } + @Test @DisplayName("Test Convert to CDF works for CDF") void convertToCdfFromCdf() { @@ -590,7 +635,7 @@ void unisynXmlCdfCountyCoroner() { @Test @DisplayName("Clear Ballot - Kansas Primary") void testClearBallotKansasPrimary() { - runTabulationTest("clear_ballot_kansas_primary"); + runTabulationTest("clear_ballot_kansas_primary", 1); } @Test @@ -909,9 +954,9 @@ void tiebreakPreviousRoundCountsThenRandomTest() { } @Test - @DisplayName("treat blank as undeclared write-in") - void treatBlankAsUndeclaredWriteInTest() { - runTabulationTest("test_set_treat_blank_as_undeclared_write_in"); + @DisplayName("treat ES&S images as undeclared write-in") + void treatImagesAsUndeclaredWriteInTest() { + runTabulationTest("test_set_treat_images_as_undeclared_write_in"); } @Test @@ -991,6 +1036,12 @@ void tabulateByPrecinctWithoutPrecincts() { @DisplayName("halting error when CVRs have a ranking larger than the max-configured value") void maxRankingValidationFails() { runTabulationTest("max_ranking_enforcement", - TabulatorSession.CastVoteRecordGenericParseException.class.toString()); + TabulatorSession.CastVoteRecordGenericParseException.class.toString()); + } + + @Test + @DisplayName("ES&S correctly ignores empty CVRs in multi-contest CVR") + void essMultiContest() { + runTabulationTest("ess_multi_contest"); } } diff --git a/src/test/resources/network/brightspots/rcv/test_data/2015_portland_mayor/2015_portland_mayor_expected_detailed_report.csv b/src/test/resources/network/brightspots/rcv/test_data/2015_portland_mayor/2015_portland_mayor_expected_detailed_report.csv index 0c16be81d..ab9bc5d3b 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/2015_portland_mayor/2015_portland_mayor_expected_detailed_report.csv +++ b/src/test/resources/network/brightspots/rcv/test_data/2015_portland_mayor/2015_portland_mayor_expected_detailed_report.csv @@ -1,5 +1,5 @@ Contest Information -Generated By,RCTab 2.0.0 +Generated By,RCTab 2.1.0 CSV Format Version,1 Type of Election,Single-Winner Contest,Portland 2015 Mayoral Race @@ -15,31 +15,31 @@ Number of Candidates,16 Total Number of Ballots,99 Number of Undervotes (No Rankings),0 -Rounds,Round 1 Votes,% of vote,transfer,Round 2 Votes,% of vote,transfer,Round 3 Votes,% of vote,transfer,Round 4 Votes,% of vote,transfer,Round 5 Votes,% of vote,transfer,Round 6 Votes,% of vote,transfer,Round 7 Votes,% of vote,transfer,Round 8 Votes,% of vote,transfer,Round 9 Votes,% of vote,transfer,Round 10 Votes,% of vote,transfer,Round 11 Votes,% of vote,transfer,Round 12 Votes,% of vote,transfer,Round 13 Votes,% of vote,transfer,Round 14 Votes,% of vote,transfer -Eliminated,"Strimling, Ethan K.; Undeclared Write-ins",,,"Rathband, Jed*",,,"Marshall, David A.",,,"Eder, John M.",,,"Miller, Markos S.",,,"Brennan, Michael F.",,,"Vail, Christopher L.*",,,"Lapchick, Jodie L.",,,"Duson, Jill C.",,,"Haadoow, Hamza A.",,,"Bryant, Peter G.",,,"Dodge, Richard A.",,,"Bragdon, Charles E.",,,,, -Elected,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"Mavodones, Nicholas M. Jr.",, -"Mavodones, Nicholas M. Jr.",13,13.26%,0,13,13.26%,2,15,15.3%,0,15,15.3%,0,15,15.3%,5,20,20.4%,0,20,20.4%,6,26,26.8%,6,32,32.98%,2,34,35.05%,7,41,42.26%,1,42,43.29%,3,45,46.39%,9,54,57.44%,0 -"Carmona, Ralph C.",12,12.24%,0,12,12.24%,0,12,12.24%,0,12,12.24%,0,12,12.24%,0,12,12.24%,1,13,13.26%,0,13,13.4%,0,13,13.4%,0,13,13.4%,2,15,15.46%,2,17,17.52%,11,28,28.86%,12,40,42.55%,0 -"Bragdon, Charles E.",11,11.22%,0,11,11.22%,0,11,11.22%,0,11,11.22%,0,11,11.22%,0,11,11.22%,3,14,14.28%,0,14,14.43%,0,14,14.43%,0,14,14.43%,0,14,14.43%,9,23,23.71%,1,24,24.74%,-24,0,0.0%,0 -"Dodge, Richard A.",10,10.2%,0,10,10.2%,0,10,10.2%,0,10,10.2%,0,10,10.2%,0,10,10.2%,0,10,10.2%,0,10,10.3%,0,10,10.3%,5,15,15.46%,0,15,15.46%,0,15,15.46%,-15,0,0.0%,0,0,0.0%,0 -"Bryant, Peter G.",9,9.18%,0,9,9.18%,0,9,9.18%,0,9,9.18%,0,9,9.18%,0,9,9.18%,2,11,11.22%,0,11,11.34%,0,11,11.34%,1,12,12.37%,0,12,12.37%,-12,0,0.0%,0,0,0.0%,0,0,0.0%,0 -"Haadoow, Hamza A.",8,8.16%,0,8,8.16%,0,8,8.16%,0,8,8.16%,0,8,8.16%,0,8,8.16%,0,8,8.16%,0,8,8.24%,1,9,9.27%,0,9,9.27%,-9,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 -"Lapchick, Jodie L.",7,7.14%,0,7,7.14%,0,7,7.14%,0,7,7.14%,0,7,7.14%,0,7,7.14%,0,7,7.14%,0,7,7.21%,-7,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 -"Brennan, Michael F.",6,6.12%,0,6,6.12%,0,6,6.12%,0,6,6.12%,0,6,6.12%,0,6,6.12%,-6,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 -"Vail, Christopher L.",6,6.12%,1,7,7.14%,0,7,7.14%,0,7,7.14%,0,7,7.14%,0,7,7.14%,0,7,7.14%,-7,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 -"Duson, Jill C.",5,5.1%,0,5,5.1%,0,5,5.1%,0,5,5.1%,3,8,8.16%,0,8,8.16%,0,8,8.16%,0,8,8.24%,0,8,8.24%,-8,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 -"Miller, Markos S.",3,3.06%,0,3,3.06%,0,3,3.06%,2,5,5.1%,0,5,5.1%,-5,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 -"Eder, John M.",3,3.06%,0,3,3.06%,0,3,3.06%,0,3,3.06%,-3,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 -"Marshall, David A.",2,2.04%,0,2,2.04%,0,2,2.04%,-2,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 -"Rathband, Jed",2,2.04%,0,2,2.04%,-2,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 -"Strimling, Ethan K.",1,1.02%,-1,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 -Undeclared Write-ins,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 -Active Ballots,98,,,98,,,98,,,98,,,98,,,98,,,98,,,97,,,97,,,97,,,97,,,97,,,97,,,94,, -Current Round Threshold,50,,,50,,,50,,,50,,,50,,,50,,,50,,,49,,,49,,,49,,,49,,,49,,,49,,,48,, -Inactive Ballots by Overvotes,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0,1,,2,3,,0 -Inactive Ballots by Skipped Rankings,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,1,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0 -Inactive Ballots by Exhausted Choices,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,1,1,,0 -Inactive Ballots by Repeated Rankings,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0 -Inactive Ballots Total,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0,1,,1,2,,0,2,,0,2,,0,2,,0,2,,0,2,,3,5,,0 +Rounds,Round 1 Votes,% of vote,transfer,Round 2 Votes,% of vote,transfer,Round 3 Votes,% of vote,transfer,Round 4 Votes,% of vote,transfer,Round 5 Votes,% of vote,transfer,Round 6 Votes,% of vote,transfer,Round 7 Votes,% of vote,transfer,Round 8 Votes,% of vote,transfer,Round 9 Votes,% of vote,transfer,Round 10 Votes,% of vote,transfer,Round 11 Votes,% of vote,transfer,Round 12 Votes,% of vote,transfer,Round 13 Votes,% of vote,transfer,Round 14 Votes,% of vote,transfer,Round 15 Votes,% of vote,transfer +Eliminated,Undeclared Write-ins,,,"Strimling, Ethan K.",,,"Rathband, Jed*",,,"Marshall, David A.",,,"Eder, John M.",,,"Miller, Markos S.",,,"Brennan, Michael F.",,,"Vail, Christopher L.*",,,"Lapchick, Jodie L.",,,"Duson, Jill C.",,,"Haadoow, Hamza A.",,,"Bryant, Peter G.",,,"Dodge, Richard A.",,,"Bragdon, Charles E.",,,,, +Elected,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"Mavodones, Nicholas M. Jr.",, +"Mavodones, Nicholas M. Jr.",13,13.26%,0,13,13.26%,0,13,13.26%,2,15,15.3%,0,15,15.3%,0,15,15.3%,5,20,20.4%,0,20,20.4%,6,26,26.8%,6,32,32.98%,2,34,35.05%,7,41,42.26%,1,42,43.29%,3,45,46.39%,9,54,57.44%,0 +"Carmona, Ralph C.",12,12.24%,0,12,12.24%,0,12,12.24%,0,12,12.24%,0,12,12.24%,0,12,12.24%,0,12,12.24%,1,13,13.26%,0,13,13.4%,0,13,13.4%,0,13,13.4%,2,15,15.46%,2,17,17.52%,11,28,28.86%,12,40,42.55%,0 +"Bragdon, Charles E.",11,11.22%,0,11,11.22%,0,11,11.22%,0,11,11.22%,0,11,11.22%,0,11,11.22%,0,11,11.22%,3,14,14.28%,0,14,14.43%,0,14,14.43%,0,14,14.43%,0,14,14.43%,9,23,23.71%,1,24,24.74%,-24,0,0.0%,0 +"Dodge, Richard A.",10,10.2%,0,10,10.2%,0,10,10.2%,0,10,10.2%,0,10,10.2%,0,10,10.2%,0,10,10.2%,0,10,10.2%,0,10,10.3%,0,10,10.3%,5,15,15.46%,0,15,15.46%,0,15,15.46%,-15,0,0.0%,0,0,0.0%,0 +"Bryant, Peter G.",9,9.18%,0,9,9.18%,0,9,9.18%,0,9,9.18%,0,9,9.18%,0,9,9.18%,0,9,9.18%,2,11,11.22%,0,11,11.34%,0,11,11.34%,1,12,12.37%,0,12,12.37%,-12,0,0.0%,0,0,0.0%,0,0,0.0%,0 +"Haadoow, Hamza A.",8,8.16%,0,8,8.16%,0,8,8.16%,0,8,8.16%,0,8,8.16%,0,8,8.16%,0,8,8.16%,0,8,8.16%,0,8,8.24%,1,9,9.27%,0,9,9.27%,-9,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 +"Lapchick, Jodie L.",7,7.14%,0,7,7.14%,0,7,7.14%,0,7,7.14%,0,7,7.14%,0,7,7.14%,0,7,7.14%,0,7,7.14%,0,7,7.21%,-7,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 +"Brennan, Michael F.",6,6.12%,0,6,6.12%,0,6,6.12%,0,6,6.12%,0,6,6.12%,0,6,6.12%,0,6,6.12%,-6,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 +"Duson, Jill C.",5,5.1%,0,5,5.1%,0,5,5.1%,0,5,5.1%,0,5,5.1%,3,8,8.16%,0,8,8.16%,0,8,8.16%,0,8,8.24%,0,8,8.24%,-8,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 +"Vail, Christopher L.",5,5.1%,1,6,6.12%,1,7,7.14%,0,7,7.14%,0,7,7.14%,0,7,7.14%,0,7,7.14%,0,7,7.14%,-7,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 +"Miller, Markos S.",3,3.06%,0,3,3.06%,0,3,3.06%,0,3,3.06%,2,5,5.1%,0,5,5.1%,-5,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 +"Eder, John M.",3,3.06%,0,3,3.06%,0,3,3.06%,0,3,3.06%,0,3,3.06%,-3,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 +"Marshall, David A.",2,2.04%,0,2,2.04%,0,2,2.04%,0,2,2.04%,-2,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 +"Rathband, Jed",2,2.04%,0,2,2.04%,0,2,2.04%,-2,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 +"Strimling, Ethan K.",1,1.02%,0,1,1.02%,-1,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 +Undeclared Write-ins,1,1.02%,-1,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 +Active Ballots,98,,,98,,,98,,,98,,,98,,,98,,,98,,,98,,,97,,,97,,,97,,,97,,,97,,,97,,,94,, +Current Round Threshold,50,,,50,,,50,,,50,,,50,,,50,,,50,,,50,,,49,,,49,,,49,,,49,,,49,,,49,,,48,, +Inactive Ballots by Overvotes,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0,1,,2,3,,0 +Inactive Ballots by Skipped Rankings,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,1,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0 +Inactive Ballots by Exhausted Choices,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,1,1,,0 +Inactive Ballots by Repeated Rankings,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0 +Inactive Ballots Total,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0,1,,0,1,,1,2,,0,2,,0,2,,0,2,,0,2,,0,2,,3,5,,0 *Tie resolved in accordance with election law diff --git a/src/test/resources/network/brightspots/rcv/test_data/2015_portland_mayor/2015_portland_mayor_expected_detailed_report.json b/src/test/resources/network/brightspots/rcv/test_data/2015_portland_mayor/2015_portland_mayor_expected_detailed_report.json index 545b88088..1274b6d59 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/2015_portland_mayor/2015_portland_mayor_expected_detailed_report.json +++ b/src/test/resources/network/brightspots/rcv/test_data/2015_portland_mayor/2015_portland_mayor_expected_detailed_report.json @@ -30,7 +30,39 @@ "Miller, Markos S." : "3", "Rathband, Jed" : "2", "Strimling, Ethan K." : "1", - "Undeclared Write-ins" : "0", + "Undeclared Write-ins" : "1", + "Vail, Christopher L." : "5" + }, + "tallyResults" : [ { + "eliminated" : "Undeclared Write-ins", + "transfers" : { + "Vail, Christopher L." : "1" + } + } ], + "threshold" : "50" + }, { + "inactiveBallots" : { + "exhaustedChoices" : "0", + "overvotes" : "1", + "repeatedRankings" : "0", + "skippedRankings" : "0" + }, + "round" : 2, + "tally" : { + "Bragdon, Charles E." : "11", + "Brennan, Michael F." : "6", + "Bryant, Peter G." : "9", + "Carmona, Ralph C." : "12", + "Dodge, Richard A." : "10", + "Duson, Jill C." : "5", + "Eder, John M." : "3", + "Haadoow, Hamza A." : "8", + "Lapchick, Jodie L." : "7", + "Marshall, David A." : "2", + "Mavodones, Nicholas M. Jr." : "13", + "Miller, Markos S." : "3", + "Rathband, Jed" : "2", + "Strimling, Ethan K." : "1", "Vail, Christopher L." : "6" }, "tallyResults" : [ { @@ -38,9 +70,6 @@ "transfers" : { "Vail, Christopher L." : "1" } - }, { - "eliminated" : "Undeclared Write-ins", - "transfers" : { } } ], "threshold" : "50" }, { @@ -50,7 +79,7 @@ "repeatedRankings" : "0", "skippedRankings" : "0" }, - "round" : 2, + "round" : 3, "tally" : { "Bragdon, Charles E." : "11", "Brennan, Michael F." : "6", @@ -81,7 +110,7 @@ "repeatedRankings" : "0", "skippedRankings" : "0" }, - "round" : 3, + "round" : 4, "tally" : { "Bragdon, Charles E." : "11", "Brennan, Michael F." : "6", @@ -111,7 +140,7 @@ "repeatedRankings" : "0", "skippedRankings" : "0" }, - "round" : 4, + "round" : 5, "tally" : { "Bragdon, Charles E." : "11", "Brennan, Michael F." : "6", @@ -140,7 +169,7 @@ "repeatedRankings" : "0", "skippedRankings" : "0" }, - "round" : 5, + "round" : 6, "tally" : { "Bragdon, Charles E." : "11", "Brennan, Michael F." : "6", @@ -168,7 +197,7 @@ "repeatedRankings" : "0", "skippedRankings" : "0" }, - "round" : 6, + "round" : 7, "tally" : { "Bragdon, Charles E." : "11", "Brennan, Michael F." : "6", @@ -197,7 +226,7 @@ "repeatedRankings" : "0", "skippedRankings" : "0" }, - "round" : 7, + "round" : 8, "tally" : { "Bragdon, Charles E." : "14", "Bryant, Peter G." : "11", @@ -224,7 +253,7 @@ "repeatedRankings" : "0", "skippedRankings" : "1" }, - "round" : 8, + "round" : 9, "tally" : { "Bragdon, Charles E." : "14", "Bryant, Peter G." : "11", @@ -250,7 +279,7 @@ "repeatedRankings" : "0", "skippedRankings" : "1" }, - "round" : 9, + "round" : 10, "tally" : { "Bragdon, Charles E." : "14", "Bryant, Peter G." : "11", @@ -276,7 +305,7 @@ "repeatedRankings" : "0", "skippedRankings" : "1" }, - "round" : 10, + "round" : 11, "tally" : { "Bragdon, Charles E." : "14", "Bryant, Peter G." : "12", @@ -300,7 +329,7 @@ "repeatedRankings" : "0", "skippedRankings" : "1" }, - "round" : 11, + "round" : 12, "tally" : { "Bragdon, Charles E." : "14", "Bryant, Peter G." : "12", @@ -324,7 +353,7 @@ "repeatedRankings" : "0", "skippedRankings" : "1" }, - "round" : 12, + "round" : 13, "tally" : { "Bragdon, Charles E." : "23", "Carmona, Ralph C." : "17", @@ -347,7 +376,7 @@ "repeatedRankings" : "0", "skippedRankings" : "1" }, - "round" : 13, + "round" : 14, "tally" : { "Bragdon, Charles E." : "24", "Carmona, Ralph C." : "28", @@ -369,7 +398,7 @@ "repeatedRankings" : "0", "skippedRankings" : "1" }, - "round" : 14, + "round" : 15, "tally" : { "Carmona, Ralph C." : "40", "Mavodones, Nicholas M. Jr." : "54" @@ -387,4 +416,4 @@ "totalNumBallots" : "99", "undervotes" : 0 } -} \ No newline at end of file +} diff --git a/src/test/resources/network/brightspots/rcv/test_data/2015_portland_mayor_codes/2015_portland_mayor_codes_cvr.xlsx b/src/test/resources/network/brightspots/rcv/test_data/2015_portland_mayor_codes/2015_portland_mayor_codes_cvr.xlsx index 95cbeb10b..86f5d4117 100644 Binary files a/src/test/resources/network/brightspots/rcv/test_data/2015_portland_mayor_codes/2015_portland_mayor_codes_cvr.xlsx and b/src/test/resources/network/brightspots/rcv/test_data/2015_portland_mayor_codes/2015_portland_mayor_codes_cvr.xlsx differ diff --git a/src/test/resources/network/brightspots/rcv/test_data/_shared/bottoms_up_cvr.xlsx b/src/test/resources/network/brightspots/rcv/test_data/_shared/bottoms_up_cvr.xlsx index 8f9500353..96e4b1697 100644 Binary files a/src/test/resources/network/brightspots/rcv/test_data/_shared/bottoms_up_cvr.xlsx and b/src/test/resources/network/brightspots/rcv/test_data/_shared/bottoms_up_cvr.xlsx differ diff --git a/src/test/resources/network/brightspots/rcv/test_data/_shared/same_data_different_formats/base_config.json b/src/test/resources/network/brightspots/rcv/test_data/_shared/same_data_different_formats/base_config.json new file mode 100644 index 000000000..e0836dd32 --- /dev/null +++ b/src/test/resources/network/brightspots/rcv/test_data/_shared/same_data_different_formats/base_config.json @@ -0,0 +1,52 @@ +{ + "tabulatorVersion" : "TEST", + "outputSettings" : { + "contestName" : "Aliases test (JSON CDF Format)", + "outputDirectory" : "output", + "contestDate" : "2023-03-23", + "contestJurisdiction" : "TestSets_Single_winner", + "contestOffice" : "Aliases test", + "tabulateByBatch" : false, + "tabulateByPrecinct" : false, + "generateCdfJson" : true + }, + "cvrFileSources" : [], + "candidates" : [ { + "name" : "Candidate A Name", + "excluded" : false, + "aliases" : [ ] + }, { + "name" : "Candidate B Name", + "excluded" : false, + "aliases" : [ ] + }, { + "name" : "Candidate C Name", + "excluded" : false, + "aliases" : [ ] + }, { + "name" : "Candidate D Name", + "excluded" : false, + "aliases" : [ ] + } ], + "rules" : { + "tiebreakMode" : "random", + "overvoteRule" : "alwaysSkipToNextRank", + "winnerElectionMode" : "singleWinnerMajority", + "randomSeed" : "0", + "numberOfWinners" : "1", + "multiSeatBottomsUpPercentageThreshold" : "", + "decimalPlacesForVoteArithmetic" : "1", + "minimumVoteThreshold" : "0", + "maxSkippedRanksAllowed" : "4", + "maxRankingsAllowed" : "4", + "nonIntegerWinningThreshold" : false, + "doesFirstRoundDetermineThreshold" : false, + "hareQuota" : false, + "batchElimination" : false, + "continueUntilTwoCandidatesRemain" : false, + "stopTabulationEarlyAfterRound" : "", + "exhaustOnDuplicateCandidate" : false, + "rulesDescription" : "Duplicate choice skip", + "treatBlankAsUndeclaredWriteIn" : false + } +} \ No newline at end of file diff --git a/src/test/resources/network/brightspots/rcv/test_data/_shared/same_data_different_formats/cdf_config.json b/src/test/resources/network/brightspots/rcv/test_data/_shared/same_data_different_formats/cdf_config.json new file mode 100644 index 000000000..a01db2869 --- /dev/null +++ b/src/test/resources/network/brightspots/rcv/test_data/_shared/same_data_different_formats/cdf_config.json @@ -0,0 +1,27 @@ +[ + { + "filePath" : "cdf_cvr_1.json", + "contestId" : "aliases file 1", + "idColumnIndex" : "", + "batchColumnIndex" : "", + "precinctColumnIndex" : "", + "overvoteDelimiter" : "", + "provider" : "cdf", + "overvoteLabel" : "", + "skippedRankLabel" : "", + "undeclaredWriteInLabel" : "", + "treatBlankAsUndeclaredWriteIn" : false + }, { + "filePath" : "cdf_cvr_2.json", + "contestId" : "aliases file 2", + "idColumnIndex" : "", + "batchColumnIndex" : "", + "precinctColumnIndex" : "", + "overvoteDelimiter" : "", + "provider" : "cdf", + "overvoteLabel" : "", + "skippedRankLabel" : "", + "undeclaredWriteInLabel" : "", + "treatBlankAsUndeclaredWriteIn" : false + } +] \ No newline at end of file diff --git a/src/test/resources/network/brightspots/rcv/test_data/_shared/same_data_different_formats/cdf_cvr_1.json b/src/test/resources/network/brightspots/rcv/test_data/_shared/same_data_different_formats/cdf_cvr_1.json new file mode 100644 index 000000000..39dafc4c9 --- /dev/null +++ b/src/test/resources/network/brightspots/rcv/test_data/_shared/same_data_different_formats/cdf_cvr_1.json @@ -0,0 +1,836 @@ +{ + "@type" : "CVR.CastVoteRecordReport", + "CVR" : [ { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "1", + "CVRSnapshot" : [ { + "@id" : "ballot-1", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + }, { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-1", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "2", + "CVRSnapshot" : [ { + "@id" : "ballot-2", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-2", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "3", + "CVRSnapshot" : [ { + "@id" : "ballot-3", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-3", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "4", + "CVRSnapshot" : [ { + "@id" : "ballot-4", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-4", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "5", + "CVRSnapshot" : [ { + "@id" : "ballot-5", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-5", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "6", + "CVRSnapshot" : [ { + "@id" : "ballot-6", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-6", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "7", + "CVRSnapshot" : [ { + "@id" : "ballot-7", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-7", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "8", + "CVRSnapshot" : [ { + "@id" : "ballot-8", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-8", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "9", + "CVRSnapshot" : [ { + "@id" : "ballot-9", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-9", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "10", + "CVRSnapshot" : [ { + "@id" : "ballot-10", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-10", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "11", + "CVRSnapshot" : [ { + "@id" : "ballot-11", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-11", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "12", + "CVRSnapshot" : [ { + "@id" : "ballot-12", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-12", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "13", + "CVRSnapshot" : [ { + "@id" : "ballot-13", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-13", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "14", + "CVRSnapshot" : [ { + "@id" : "ballot-14", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-14", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "15", + "CVRSnapshot" : [ { + "@id" : "ballot-15", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-15", + "ElectionId" : "election-001" + } ], + "Election" : [ { + "@id" : "election-001", + "@type" : "CVR.Election", + "Candidate" : [ + { + "@id" : "candidate-a", + "Name" : "Candidate A Name" + }, + { + "@id" : "candidate-b", + "Name" : "Candidate B Name" + }, + { + "@id" : "candidate-c", + "Name" : "Candidate C Name" + }, + { + "@id" : "candidate-d", + "Name" : "Candidate D Name" + } + ], + "Contest" : [ { + "@id" : "contest-001", + "@type" : "CandidateContest", + "Name" : "aliases file 1", + "ContestSelection" : [ { + "@id" : "cs-a", + "@type" : "CandidateSelection", + "CandidateIds" : [ "candidate-a" ] + }, { + "@id" : "cs-b", + "@type" : "CandidateSelection", + "CandidateIds" : [ "candidate-b" ] + }, { + "@id" : "cs-c", + "@type" : "CandidateSelection", + "CandidateIds" : [ "candidate-c" ] + }, { + "@id" : "cs-d", + "@type" : "CandidateSelection", + "CandidateIds" : [ "candidate-d" ] + } ] + } ], + "ElectionScopeId" : "gpu-election" + } ], +"GeneratedDate" : "2019-05-01T17:10:50-07:00", + "GpUnit" : [ { + "@id" : "gpu-election", + "@type" : "CVR.GpUnit", + "Name" : "TestSets_Single_winner", + "OtherType" : "Election Scope Jurisdiction", + "Type" : "other" + } ], + "ReportGeneratingDeviceIds" : [ "rd-001" ], + "ReportingDevice" : [ { + "@id" : "rd-001", + "@type" : "CVR.ReportingDevice", + "Application" : "RCTab", + "Manufacturer" : "Bright Spots" + } ], + "Version" : "1.0.0" +} diff --git a/src/test/resources/network/brightspots/rcv/test_data/_shared/same_data_different_formats/cdf_cvr_2.json b/src/test/resources/network/brightspots/rcv/test_data/_shared/same_data_different_formats/cdf_cvr_2.json new file mode 100644 index 000000000..b235e06a5 --- /dev/null +++ b/src/test/resources/network/brightspots/rcv/test_data/_shared/same_data_different_formats/cdf_cvr_2.json @@ -0,0 +1,836 @@ +{ + "@type" : "CVR.CastVoteRecordReport", + "CVR" : [ { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "1", + "CVRSnapshot" : [ { + "@id" : "ballot-1", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + }, { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-1", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "2", + "CVRSnapshot" : [ { + "@id" : "ballot-2", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-2", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "3", + "CVRSnapshot" : [ { + "@id" : "ballot-3", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-3", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "4", + "CVRSnapshot" : [ { + "@id" : "ballot-4", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-4", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "5", + "CVRSnapshot" : [ { + "@id" : "ballot-5", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-5", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "6", + "CVRSnapshot" : [ { + "@id" : "ballot-6", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-6", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "7", + "CVRSnapshot" : [ { + "@id" : "ballot-7", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-7", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "8", + "CVRSnapshot" : [ { + "@id" : "ballot-8", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-8", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "9", + "CVRSnapshot" : [ { + "@id" : "ballot-9", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-9", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "10", + "CVRSnapshot" : [ { + "@id" : "ballot-10", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-10", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "11", + "CVRSnapshot" : [ { + "@id" : "ballot-11", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-11", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "12", + "CVRSnapshot" : [ { + "@id" : "ballot-12", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-12", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "13", + "CVRSnapshot" : [ { + "@id" : "ballot-13", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-13", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "14", + "CVRSnapshot" : [ { + "@id" : "ballot-14", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-14", + "ElectionId" : "election-001" + }, { + "@type" : "CVR.CVR", + "BallotPrePrintedId" : "15", + "CVRSnapshot" : [ { + "@id" : "ballot-15", + "@type" : "CVR.CVRSnapshot", + "CVRContest" : [ { + "@type" : "CVR.CVRContest", + "CVRContestSelection" : [ { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-a", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 1 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-b", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 2 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-d", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 3 + } ] + }, { + "@type" : "CVR.CVRContestSelection", + "ContestSelectionId" : "cs-c", + "SelectionPosition" : [ { + "@type" : "CVR.SelectionPosition", + "HasIndication" : "yes", + "IsAllocable" : "unknown", + "NumberVotes" : 1, + "Rank" : 4 + } ] + } ], + "ContestId" : "contest-001" + } ], + "Type" : "original" + } ], + "CurrentSnapshotId" : "ballot-15", + "ElectionId" : "election-001" + } ], + "Election" : [ { + "@id" : "election-001", + "@type" : "CVR.Election", + "Candidate" : [ + { + "@id" : "candidate-a", + "Name" : "Candidate A Name" + }, + { + "@id" : "candidate-b", + "Name" : "Candidate B Name" + }, + { + "@id" : "candidate-c", + "Name" : "Candidate C Name" + }, + { + "@id" : "candidate-d", + "Name" : "Candidate D Name" + } + ], + "Contest" : [ { + "@id" : "contest-001", + "@type" : "CandidateContest", + "Name" : "aliases file 2", + "ContestSelection" : [ { + "@id" : "cs-a", + "@type" : "CandidateSelection", + "CandidateIds" : [ "candidate-a" ] + }, { + "@id" : "cs-b", + "@type" : "CandidateSelection", + "CandidateIds" : [ "candidate-b" ] + }, { + "@id" : "cs-c", + "@type" : "CandidateSelection", + "CandidateIds" : [ "candidate-c" ] + }, { + "@id" : "cs-d", + "@type" : "CandidateSelection", + "CandidateIds" : [ "candidate-d" ] + } ] + } ], + "ElectionScopeId" : "gpu-election" + } ], +"GeneratedDate" : "2019-05-01T17:10:50-07:00", + "GpUnit" : [ { + "@id" : "gpu-election", + "@type" : "CVR.GpUnit", + "Name" : "TestSets_Single_winner", + "OtherType" : "Election Scope Jurisdiction", + "Type" : "other" + } ], + "ReportGeneratingDeviceIds" : [ "rd-001" ], + "ReportingDevice" : [ { + "@id" : "rd-001", + "@type" : "CVR.ReportingDevice", + "Application" : "RCTab", + "Manufacturer" : "Bright Spots" + } ], + "Version" : "1.0.0" +} diff --git a/src/test/resources/network/brightspots/rcv/test_data/_shared/same_data_different_formats/ess_config.json b/src/test/resources/network/brightspots/rcv/test_data/_shared/same_data_different_formats/ess_config.json new file mode 100644 index 000000000..21d6e6e35 --- /dev/null +++ b/src/test/resources/network/brightspots/rcv/test_data/_shared/same_data_different_formats/ess_config.json @@ -0,0 +1,17 @@ +[ + { + "filePath" : "ess_cvr.xlsx", + "contestId" : "", + "firstVoteColumnIndex" : "6", + "firstVoteRowIndex" : "3", + "idColumnIndex" : "3", + "batchColumnIndex" : "", + "precinctColumnIndex" : "", + "overvoteDelimiter" : "", + "provider" : "ess", + "overvoteLabel" : "overvote", + "skippedRankLabel" : "undervote", + "undeclaredWriteInLabel" : "", + "treatBlankAsUndeclaredWriteIn" : false + } +] \ No newline at end of file diff --git a/src/test/resources/network/brightspots/rcv/test_data/_shared/same_data_different_formats/ess_cvr.xlsx b/src/test/resources/network/brightspots/rcv/test_data/_shared/same_data_different_formats/ess_cvr.xlsx new file mode 100644 index 000000000..c06c37648 Binary files /dev/null and b/src/test/resources/network/brightspots/rcv/test_data/_shared/same_data_different_formats/ess_cvr.xlsx differ diff --git a/src/test/resources/network/brightspots/rcv/test_data/_shared/simple_ess_cvr.xlsx b/src/test/resources/network/brightspots/rcv/test_data/_shared/simple_ess_cvr.xlsx index 1e6b4744b..6dba45d75 100644 Binary files a/src/test/resources/network/brightspots/rcv/test_data/_shared/simple_ess_cvr.xlsx and b/src/test/resources/network/brightspots/rcv/test_data/_shared/simple_ess_cvr.xlsx differ diff --git a/src/test/resources/network/brightspots/rcv/test_data/_shared/simple_sequential_cvr.xlsx b/src/test/resources/network/brightspots/rcv/test_data/_shared/simple_sequential_cvr.xlsx index b96ef32fd..ad5462059 100644 Binary files a/src/test/resources/network/brightspots/rcv/test_data/_shared/simple_sequential_cvr.xlsx and b/src/test/resources/network/brightspots/rcv/test_data/_shared/simple_sequential_cvr.xlsx differ diff --git a/src/test/resources/network/brightspots/rcv/test_data/_shared/tiebreak_cvr.xlsx b/src/test/resources/network/brightspots/rcv/test_data/_shared/tiebreak_cvr.xlsx index e5e49afbe..b6ef31939 100644 Binary files a/src/test/resources/network/brightspots/rcv/test_data/_shared/tiebreak_cvr.xlsx and b/src/test/resources/network/brightspots/rcv/test_data/_shared/tiebreak_cvr.xlsx differ diff --git a/src/test/resources/network/brightspots/rcv/test_data/clear_ballot_kansas_primary/Tabulate by Batch/clear_ballot_kansas_primary_expected_ED-004_batch_detailed_report.json b/src/test/resources/network/brightspots/rcv/test_data/clear_ballot_kansas_primary/Tabulate by Batch/clear_ballot_kansas_primary_expected_ED-004_batch_detailed_report.json new file mode 100644 index 000000000..a73f0e4ea --- /dev/null +++ b/src/test/resources/network/brightspots/rcv/test_data/clear_ballot_kansas_primary/Tabulate by Batch/clear_ballot_kansas_primary_expected_ED-004_batch_detailed_report.json @@ -0,0 +1,123 @@ +{ + "config" : { + "batch" : "ED-004", + "contest" : "Democratic Primary", + "date" : "", + "generatedBy" : "RCTab 2.0.2", + "jurisdiction" : "Kansas", + "office" : "Democratic Presidential Primary" + }, + "jsonFormatVersion" : "1", + "results" : [ { + "inactiveBallots" : { + "exhaustedChoices" : "0", + "overvotes" : "0", + "repeatedRankings" : "0", + "skippedRankings" : "0" + }, + "round" : 1, + "tally" : { + "Amy Klobuchar" : "20", + "Cory Booker" : "1", + "Joe Biden" : "3", + "John Delaney" : "5", + "Julian Castro" : "4", + "Kamala Harris" : "5", + "Michael Bennet" : "5", + "Pete Buttigieg" : "2", + "Steven Bullock" : "0", + "Tulsi Gabbard" : "5", + "Undeclared Write-ins" : "0" + }, + "tallyResults" : [ { + "eliminated" : "Cory Booker", + "transfers" : { + "Amy Klobuchar" : "1" + } + }, { + "eliminated" : "Steven Bullock", + "transfers" : { } + }, { + "eliminated" : "Undeclared Write-ins", + "transfers" : { } + } ], + "threshold" : "26" + }, { + "inactiveBallots" : { + "exhaustedChoices" : "0", + "overvotes" : "0", + "repeatedRankings" : "0", + "skippedRankings" : "0" + }, + "round" : 2, + "tally" : { + "Amy Klobuchar" : "21", + "Joe Biden" : "3", + "John Delaney" : "5", + "Julian Castro" : "4", + "Kamala Harris" : "5", + "Michael Bennet" : "5", + "Pete Buttigieg" : "2", + "Tulsi Gabbard" : "5" + }, + "tallyResults" : [ { + "eliminated" : "Pete Buttigieg", + "transfers" : { + "Amy Klobuchar" : "2" + } + } ], + "threshold" : "26" + }, { + "inactiveBallots" : { + "exhaustedChoices" : "0", + "overvotes" : "0", + "repeatedRankings" : "0", + "skippedRankings" : "0" + }, + "round" : 3, + "tally" : { + "Amy Klobuchar" : "23", + "Joe Biden" : "3", + "John Delaney" : "5", + "Julian Castro" : "4", + "Kamala Harris" : "5", + "Michael Bennet" : "5", + "Tulsi Gabbard" : "5" + }, + "tallyResults" : [ { + "eliminated" : "Joe Biden", + "transfers" : { + "Amy Klobuchar" : "3" + } + } ], + "threshold" : "26" + }, { + "inactiveBallots" : { + "exhaustedChoices" : "0", + "overvotes" : "0", + "repeatedRankings" : "0", + "skippedRankings" : "0" + }, + "round" : 4, + "tally" : { + "Amy Klobuchar" : "26", + "John Delaney" : "5", + "Julian Castro" : "4", + "Kamala Harris" : "5", + "Michael Bennet" : "5", + "Tulsi Gabbard" : "5" + }, + "tallyResults" : [ { + "elected" : "Amy Klobuchar", + "transfers" : { } + } ], + "threshold" : "26" + } ], + "summary" : { + "finalThreshold" : "26", + "numCandidates" : 11, + "numWinners" : 1, + "totalNumBallots" : "50", + "undervotes" : 0 + } +} \ No newline at end of file diff --git a/src/test/resources/network/brightspots/rcv/test_data/clear_ballot_kansas_primary/clear_ballot_kansas_primary_config.json b/src/test/resources/network/brightspots/rcv/test_data/clear_ballot_kansas_primary/clear_ballot_kansas_primary_config.json index b35abbaf0..c51a29ec6 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/clear_ballot_kansas_primary/clear_ballot_kansas_primary_config.json +++ b/src/test/resources/network/brightspots/rcv/test_data/clear_ballot_kansas_primary/clear_ballot_kansas_primary_config.json @@ -6,7 +6,7 @@ "contestDate" : "", "contestJurisdiction" : "Kansas", "contestOffice" : "Democratic Presidential Primary", - "tabulateByBatch" : false, + "tabulateByBatch" : true, "tabulateByPrecinct" : false, "generateCdfJson" : false }, diff --git a/src/test/resources/network/brightspots/rcv/test_data/continue_tabulation_test/continue_tabulation_test_cvr.xlsx b/src/test/resources/network/brightspots/rcv/test_data/continue_tabulation_test/continue_tabulation_test_cvr.xlsx index 876b06019..ff9ffdcfa 100644 Binary files a/src/test/resources/network/brightspots/rcv/test_data/continue_tabulation_test/continue_tabulation_test_cvr.xlsx and b/src/test/resources/network/brightspots/rcv/test_data/continue_tabulation_test/continue_tabulation_test_cvr.xlsx differ diff --git a/src/test/resources/network/brightspots/rcv/test_data/continue_until_two_threshold_freeze/continue_until_two_threshold_freeze_cvr.xlsx b/src/test/resources/network/brightspots/rcv/test_data/continue_until_two_threshold_freeze/continue_until_two_threshold_freeze_cvr.xlsx index 152c983d5..e4c3230e2 100644 Binary files a/src/test/resources/network/brightspots/rcv/test_data/continue_until_two_threshold_freeze/continue_until_two_threshold_freeze_cvr.xlsx and b/src/test/resources/network/brightspots/rcv/test_data/continue_until_two_threshold_freeze/continue_until_two_threshold_freeze_cvr.xlsx differ diff --git a/src/test/resources/network/brightspots/rcv/test_data/continue_until_two_with_batch_elimination_test/continue_until_two_with_batch_elimination_test_cvr.xlsx b/src/test/resources/network/brightspots/rcv/test_data/continue_until_two_with_batch_elimination_test/continue_until_two_with_batch_elimination_test_cvr.xlsx index fe4b0399e..998ba483a 100644 Binary files a/src/test/resources/network/brightspots/rcv/test_data/continue_until_two_with_batch_elimination_test/continue_until_two_with_batch_elimination_test_cvr.xlsx and b/src/test/resources/network/brightspots/rcv/test_data/continue_until_two_with_batch_elimination_test/continue_until_two_with_batch_elimination_test_cvr.xlsx differ diff --git a/src/test/resources/network/brightspots/rcv/test_data/duplicate_test/duplicate_test_cvr.xlsx b/src/test/resources/network/brightspots/rcv/test_data/duplicate_test/duplicate_test_cvr.xlsx index 038431796..70baff24d 100644 Binary files a/src/test/resources/network/brightspots/rcv/test_data/duplicate_test/duplicate_test_cvr.xlsx and b/src/test/resources/network/brightspots/rcv/test_data/duplicate_test/duplicate_test_cvr.xlsx differ diff --git a/src/test/resources/network/brightspots/rcv/test_data/ess_multi_contest/ess_multi_contest_config.json b/src/test/resources/network/brightspots/rcv/test_data/ess_multi_contest/ess_multi_contest_config.json new file mode 100644 index 000000000..6bae58fa6 --- /dev/null +++ b/src/test/resources/network/brightspots/rcv/test_data/ess_multi_contest/ess_multi_contest_config.json @@ -0,0 +1,86 @@ +{ + "tabulatorVersion" : "TEST", + "outputSettings" : { + "contestName" : "ES&S Multi-Contest", + "outputDirectory" : "output", + "contestDate" : "", + "contestJurisdiction" : "", + "contestOffice" : "", + "tabulateByBatch" : false, + "tabulateByPrecinct" : false, + "generateCdfJson" : false + }, + "cvrFileSources" : [ { + "filePath" : "ess_multi_contest_cvr.xlsx", + "contestId" : "", + "firstVoteColumnIndex" : "9", + "firstVoteRowIndex" : "2", + "idColumnIndex" : "4", + "batchColumnIndex" : "5", + "precinctColumnIndex" : "7", + "overvoteDelimiter" : "", + "provider" : "ess", + "overvoteLabel" : "overvote", + "skippedRankLabel" : "undervote", + "undeclaredWriteInLabel" : "Write-in", + "treatBlankAsUndeclaredWriteIn" : false + } ], + "candidates" : [ { + "name" : "Mickey Mouse", + "excluded" : false, + "aliases" : [ ] + }, { + "name" : "George Washington", + "excluded" : false, + "aliases" : [ ] + }, { + "name" : "Luke Skywalker", + "excluded" : false, + "aliases" : [ ] + }, { + "name" : "Zelda", + "excluded" : false, + "aliases" : [ ] + }, { + "name" : "Abraham Lincoln", + "excluded" : false, + "aliases" : [ ] + }, { + "name" : "Michael Jackson", + "excluded" : false, + "aliases" : [ ] + }, { + "name" : "Indiana Jones", + "excluded" : false, + "aliases" : [ ] + }, { + "name" : "Bugs Bunny", + "excluded" : false, + "aliases" : [ ] + }, { + "name" : "John Oliver", + "excluded" : false, + "aliases" : [ ] + } ], + "rules" : { + "tiebreakMode" : "useCandidateOrder", + "overvoteRule" : "exhaustImmediately", + "winnerElectionMode" : "singleWinnerMajority", + "randomSeed" : "", + "numberOfWinners" : "1", + "multiSeatBottomsUpPercentageThreshold" : "", + "decimalPlacesForVoteArithmetic" : "4", + "maxSkippedRanksAllowed" : "1", + "maxRankingsAllowed" : "5", + "nonIntegerWinningThreshold" : false, + "doesFirstRoundDetermineThreshold" : false, + "hareQuota" : false, + "batchElimination" : false, + "cutoffElimination" : false, + "continueUntilTwoCandidatesRemain" : false, + "stopTabulationEarlyAfterRound" : "", + "exhaustOnDuplicateCandidate" : false, + "rulesDescription" : "", + "treatBlankAsUndeclaredWriteIn" : false + } +} \ No newline at end of file diff --git a/src/test/resources/network/brightspots/rcv/test_data/ess_multi_contest/ess_multi_contest_cvr.xlsx b/src/test/resources/network/brightspots/rcv/test_data/ess_multi_contest/ess_multi_contest_cvr.xlsx new file mode 100644 index 000000000..3f982c988 Binary files /dev/null and b/src/test/resources/network/brightspots/rcv/test_data/ess_multi_contest/ess_multi_contest_cvr.xlsx differ diff --git a/src/test/resources/network/brightspots/rcv/test_data/ess_multi_contest/ess_multi_contest_expected_detailed_report.csv b/src/test/resources/network/brightspots/rcv/test_data/ess_multi_contest/ess_multi_contest_expected_detailed_report.csv new file mode 100644 index 000000000..40893c3d7 --- /dev/null +++ b/src/test/resources/network/brightspots/rcv/test_data/ess_multi_contest/ess_multi_contest_expected_detailed_report.csv @@ -0,0 +1,39 @@ +Contest Information +Generated By,RCTab 2.0.2 +CSV Format Version,1 +Type of Election,Single-Winner +Contest,ES&S Multi-Contest +Jurisdiction, +Office, +Date, +Winner(s),Bugs Bunny +Final Threshold,18 + +Contest Summary +Number to be Elected,1 +Number of Candidates,10 +Total Number of Ballots,44 +Number of Undervotes (No Rankings),0 + +Rounds,Round 1 Votes,% of vote,transfer,Round 2 Votes,% of vote,transfer,Round 3 Votes,% of vote,transfer,Round 4 Votes,% of vote,transfer,Round 5 Votes,% of vote,transfer,Round 6 Votes,% of vote,transfer,Round 7 Votes,% of vote,transfer,Round 8 Votes,% of vote,transfer,Round 9 Votes,% of vote,transfer +Eliminated,George Washington*,,,Undeclared Write-ins,,,Abraham Lincoln*,,,Zelda,,,Mickey Mouse,,,Luke Skywalker,,,Indiana Jones,,,John Oliver,,,,, +Elected,,,,,,,,,,,,,,,,,,,,,,,,,Bugs Bunny,, +Michael Jackson,10,24.39%,0,10,24.39%,0,10,24.39%,2,12,29.26%,0,12,29.26%,2,14,34.14%,0,14,34.14%,0,14,34.14%,0,14,41.17%,0 +Bugs Bunny,7,17.07%,0,7,17.07%,0,7,17.07%,0,7,17.07%,0,7,17.07%,0,7,17.07%,3,10,24.39%,7,17,41.46%,3,20,58.82%,0 +Luke Skywalker,6,14.63%,0,6,14.63%,0,6,14.63%,0,6,14.63%,0,6,14.63%,0,6,14.63%,-6,0,0.0%,0,0,0.0%,0,0,0.0%,0 +Indiana Jones,5,12.19%,0,5,12.19%,0,5,12.19%,0,5,12.19%,2,7,17.07%,0,7,17.07%,0,7,17.07%,-7,0,0.0%,0,0,0.0%,0 +John Oliver,5,12.19%,0,5,12.19%,0,5,12.19%,0,5,12.19%,0,5,12.19%,2,7,17.07%,3,10,24.39%,0,10,24.39%,-10,0,0.0%,0 +Mickey Mouse,4,9.75%,0,4,9.75%,0,4,9.75%,0,4,9.75%,0,4,9.75%,-4,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 +Zelda,2,4.87%,0,2,4.87%,0,2,4.87%,0,2,4.87%,-2,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 +Abraham Lincoln,2,4.87%,0,2,4.87%,0,2,4.87%,-2,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 +George Washington,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 +Undeclared Write-ins,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0,0,0.0%,0 +Active Ballots,41,,,41,,,41,,,41,,,41,,,41,,,41,,,41,,,34,, +Current Round Threshold,21,,,21,,,21,,,21,,,21,,,21,,,21,,,21,,,18,, +Inactive Ballots by Overvotes,3,,0,3,,0,3,,0,3,,0,3,,0,3,,0,3,,0,3,,0,3,,0 +Inactive Ballots by Skipped Rankings,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0 +Inactive Ballots by Exhausted Choices,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,7,7,,0 +Inactive Ballots by Repeated Rankings,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0,0,,0 +Inactive Ballots Total,3,,0,3,,0,3,,0,3,,0,3,,0,3,,0,3,,0,3,,7,10,,0 + +*Tie resolved in accordance with election law diff --git a/src/test/resources/network/brightspots/rcv/test_data/ess_multi_contest/ess_multi_contest_expected_detailed_report.json b/src/test/resources/network/brightspots/rcv/test_data/ess_multi_contest/ess_multi_contest_expected_detailed_report.json new file mode 100644 index 000000000..c27a9108e --- /dev/null +++ b/src/test/resources/network/brightspots/rcv/test_data/ess_multi_contest/ess_multi_contest_expected_detailed_report.json @@ -0,0 +1,222 @@ +{ + "config" : { + "contest" : "ES&S Multi-Contest", + "date" : "", + "generatedBy" : "RCTab 2.0.2", + "jurisdiction" : "", + "office" : "" + }, + "jsonFormatVersion" : "1", + "results" : [ { + "inactiveBallots" : { + "exhaustedChoices" : "0", + "overvotes" : "3", + "repeatedRankings" : "0", + "skippedRankings" : "0" + }, + "round" : 1, + "tally" : { + "Abraham Lincoln" : "2", + "Bugs Bunny" : "7", + "George Washington" : "0", + "Indiana Jones" : "5", + "John Oliver" : "5", + "Luke Skywalker" : "6", + "Michael Jackson" : "10", + "Mickey Mouse" : "4", + "Undeclared Write-ins" : "0", + "Zelda" : "2" + }, + "tallyResults" : [ { + "eliminated" : "George Washington", + "transfers" : { } + } ], + "threshold" : "21" + }, { + "inactiveBallots" : { + "exhaustedChoices" : "0", + "overvotes" : "3", + "repeatedRankings" : "0", + "skippedRankings" : "0" + }, + "round" : 2, + "tally" : { + "Abraham Lincoln" : "2", + "Bugs Bunny" : "7", + "Indiana Jones" : "5", + "John Oliver" : "5", + "Luke Skywalker" : "6", + "Michael Jackson" : "10", + "Mickey Mouse" : "4", + "Undeclared Write-ins" : "0", + "Zelda" : "2" + }, + "tallyResults" : [ { + "eliminated" : "Undeclared Write-ins", + "transfers" : { } + } ], + "threshold" : "21" + }, { + "inactiveBallots" : { + "exhaustedChoices" : "0", + "overvotes" : "3", + "repeatedRankings" : "0", + "skippedRankings" : "0" + }, + "round" : 3, + "tally" : { + "Abraham Lincoln" : "2", + "Bugs Bunny" : "7", + "Indiana Jones" : "5", + "John Oliver" : "5", + "Luke Skywalker" : "6", + "Michael Jackson" : "10", + "Mickey Mouse" : "4", + "Zelda" : "2" + }, + "tallyResults" : [ { + "eliminated" : "Abraham Lincoln", + "transfers" : { + "Michael Jackson" : "2" + } + } ], + "threshold" : "21" + }, { + "inactiveBallots" : { + "exhaustedChoices" : "0", + "overvotes" : "3", + "repeatedRankings" : "0", + "skippedRankings" : "0" + }, + "round" : 4, + "tally" : { + "Bugs Bunny" : "7", + "Indiana Jones" : "5", + "John Oliver" : "5", + "Luke Skywalker" : "6", + "Michael Jackson" : "12", + "Mickey Mouse" : "4", + "Zelda" : "2" + }, + "tallyResults" : [ { + "eliminated" : "Zelda", + "transfers" : { + "Indiana Jones" : "2" + } + } ], + "threshold" : "21" + }, { + "inactiveBallots" : { + "exhaustedChoices" : "0", + "overvotes" : "3", + "repeatedRankings" : "0", + "skippedRankings" : "0" + }, + "round" : 5, + "tally" : { + "Bugs Bunny" : "7", + "Indiana Jones" : "7", + "John Oliver" : "5", + "Luke Skywalker" : "6", + "Michael Jackson" : "12", + "Mickey Mouse" : "4" + }, + "tallyResults" : [ { + "eliminated" : "Mickey Mouse", + "transfers" : { + "John Oliver" : "2", + "Michael Jackson" : "2" + } + } ], + "threshold" : "21" + }, { + "inactiveBallots" : { + "exhaustedChoices" : "0", + "overvotes" : "3", + "repeatedRankings" : "0", + "skippedRankings" : "0" + }, + "round" : 6, + "tally" : { + "Bugs Bunny" : "7", + "Indiana Jones" : "7", + "John Oliver" : "7", + "Luke Skywalker" : "6", + "Michael Jackson" : "14" + }, + "tallyResults" : [ { + "eliminated" : "Luke Skywalker", + "transfers" : { + "Bugs Bunny" : "3", + "John Oliver" : "3" + } + } ], + "threshold" : "21" + }, { + "inactiveBallots" : { + "exhaustedChoices" : "0", + "overvotes" : "3", + "repeatedRankings" : "0", + "skippedRankings" : "0" + }, + "round" : 7, + "tally" : { + "Bugs Bunny" : "10", + "Indiana Jones" : "7", + "John Oliver" : "10", + "Michael Jackson" : "14" + }, + "tallyResults" : [ { + "eliminated" : "Indiana Jones", + "transfers" : { + "Bugs Bunny" : "7" + } + } ], + "threshold" : "21" + }, { + "inactiveBallots" : { + "exhaustedChoices" : "0", + "overvotes" : "3", + "repeatedRankings" : "0", + "skippedRankings" : "0" + }, + "round" : 8, + "tally" : { + "Bugs Bunny" : "17", + "John Oliver" : "10", + "Michael Jackson" : "14" + }, + "tallyResults" : [ { + "eliminated" : "John Oliver", + "transfers" : { + "Bugs Bunny" : "3", + "exhausted" : "7" + } + } ], + "threshold" : "21" + }, { + "inactiveBallots" : { + "exhaustedChoices" : "7", + "overvotes" : "3", + "repeatedRankings" : "0", + "skippedRankings" : "0" + }, + "round" : 9, + "tally" : { + "Bugs Bunny" : "20", + "Michael Jackson" : "14" + }, + "tallyResults" : [ { + "elected" : "Bugs Bunny", + "transfers" : { } + } ], + "threshold" : "18" + } ], + "summary" : { + "finalThreshold" : "18", + "numCandidates" : 10, + "numWinners" : 1, + "totalNumBallots" : "44", + "undervotes" : 0 + } +} \ No newline at end of file diff --git a/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_test/first_round_determines_threshold_test_cvr.xlsx b/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_test/first_round_determines_threshold_test_cvr.xlsx index 707385247..dccb63465 100644 Binary files a/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_test/first_round_determines_threshold_test_cvr.xlsx and b/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_test/first_round_determines_threshold_test_cvr.xlsx differ diff --git a/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_test/first_round_determines_threshold_test_expected_detailed_report.csv b/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_test/first_round_determines_threshold_test_expected_detailed_report.csv index 9e88ac95c..6a9af43e1 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_test/first_round_determines_threshold_test_expected_detailed_report.csv +++ b/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_test/first_round_determines_threshold_test_expected_detailed_report.csv @@ -12,8 +12,8 @@ Final Threshold,9 Contest Summary Number to be Elected,1 Number of Candidates,3 -Total Number of Ballots,19 -Number of Undervotes (No Rankings),3 +Total Number of Ballots,16 +Number of Undervotes (No Rankings),0 Rounds,Round 1 Votes,% of vote,transfer,Round 2 Votes,% of vote,transfer Eliminated,"Haadoow, Hamza A.*",,,,, diff --git a/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_test/first_round_determines_threshold_test_expected_detailed_report.json b/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_test/first_round_determines_threshold_test_expected_detailed_report.json index f3400513b..f31cb7414 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_test/first_round_determines_threshold_test_expected_detailed_report.json +++ b/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_test/first_round_determines_threshold_test_expected_detailed_report.json @@ -49,7 +49,7 @@ "finalThreshold" : "9", "numCandidates" : 4, "numWinners" : 1, - "totalNumBallots" : "19", - "undervotes" : 3 + "totalNumBallots" : "16", + "undervotes" : 0 } -} \ No newline at end of file +} diff --git a/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_tiebreaker_test/first_round_determines_threshold_tiebreaker_test_cvr.xlsx b/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_tiebreaker_test/first_round_determines_threshold_tiebreaker_test_cvr.xlsx index 473a30733..4bc09103c 100644 Binary files a/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_tiebreaker_test/first_round_determines_threshold_tiebreaker_test_cvr.xlsx and b/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_tiebreaker_test/first_round_determines_threshold_tiebreaker_test_cvr.xlsx differ diff --git a/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_tiebreaker_test/first_round_determines_threshold_tiebreaker_test_expected_detailed_report.csv b/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_tiebreaker_test/first_round_determines_threshold_tiebreaker_test_expected_detailed_report.csv index 06177766c..97070a4f4 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_tiebreaker_test/first_round_determines_threshold_tiebreaker_test_expected_detailed_report.csv +++ b/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_tiebreaker_test/first_round_determines_threshold_tiebreaker_test_expected_detailed_report.csv @@ -12,8 +12,8 @@ Final Threshold,9 Contest Summary Number to be Elected,1 Number of Candidates,3 -Total Number of Ballots,19 -Number of Undervotes (No Rankings),2 +Total Number of Ballots,17 +Number of Undervotes (No Rankings),0 Rounds,Round 1 Votes,% of vote,transfer,Round 2 Votes,% of vote,transfer Eliminated,"Haadoow, Hamza A.",,,,, diff --git a/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_tiebreaker_test/first_round_determines_threshold_tiebreaker_test_expected_detailed_report.json b/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_tiebreaker_test/first_round_determines_threshold_tiebreaker_test_expected_detailed_report.json index a6236fb37..b0be11052 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_tiebreaker_test/first_round_determines_threshold_tiebreaker_test_expected_detailed_report.json +++ b/src/test/resources/network/brightspots/rcv/test_data/first_round_determines_threshold_tiebreaker_test/first_round_determines_threshold_tiebreaker_test_expected_detailed_report.json @@ -49,7 +49,7 @@ "finalThreshold" : "9", "numCandidates" : 4, "numWinners" : 1, - "totalNumBallots" : "19", - "undervotes" : 2 + "totalNumBallots" : "17", + "undervotes" : 0 } } \ No newline at end of file diff --git a/src/test/resources/network/brightspots/rcv/test_data/more_winners_than_candidates/more_winners_than_candidates_config.json b/src/test/resources/network/brightspots/rcv/test_data/more_winners_than_candidates/more_winners_than_candidates_config.json index 89bee075d..a84b32038 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/more_winners_than_candidates/more_winners_than_candidates_config.json +++ b/src/test/resources/network/brightspots/rcv/test_data/more_winners_than_candidates/more_winners_than_candidates_config.json @@ -52,7 +52,7 @@ "decimalPlacesForVoteArithmetic" : "4", "minimumVoteThreshold" : "0", "maxSkippedRanksAllowed" : "1", - "maxRankingsAllowed" : "8", + "maxRankingsAllowed" : "3", "nonIntegerWinningThreshold" : false, "doesFirstRoundDetermineThreshold" : false, "hareQuota" : false, diff --git a/src/test/resources/network/brightspots/rcv/test_data/stop_tabulation_early_test/stop_tabulation_early_test_config.json b/src/test/resources/network/brightspots/rcv/test_data/stop_tabulation_early_test/stop_tabulation_early_test_config.json index e0ecdd464..a8b2264d4 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/stop_tabulation_early_test/stop_tabulation_early_test_config.json +++ b/src/test/resources/network/brightspots/rcv/test_data/stop_tabulation_early_test/stop_tabulation_early_test_config.json @@ -48,7 +48,7 @@ "decimalPlacesForVoteArithmetic" : "4", "minimumVoteThreshold" : "0", "maxSkippedRanksAllowed" : "0", - "maxRankingsAllowed" : "3", + "maxRankingsAllowed" : "1", "nonIntegerWinningThreshold" : false, "doesFirstRoundDetermineThreshold" : false, "hareQuota" : false, diff --git a/src/test/resources/network/brightspots/rcv/test_data/test_set_treat_blank_as_undeclared_write_in/test_set_treat_blank_as_undeclared_write_in_cvr.xlsx b/src/test/resources/network/brightspots/rcv/test_data/test_set_treat_blank_as_undeclared_write_in/test_set_treat_blank_as_undeclared_write_in_cvr.xlsx deleted file mode 100644 index 48b41a61b..000000000 Binary files a/src/test/resources/network/brightspots/rcv/test_data/test_set_treat_blank_as_undeclared_write_in/test_set_treat_blank_as_undeclared_write_in_cvr.xlsx and /dev/null differ diff --git a/src/test/resources/network/brightspots/rcv/test_data/test_set_treat_blank_as_undeclared_write_in/test_set_treat_blank_as_undeclared_write_in_config.json b/src/test/resources/network/brightspots/rcv/test_data/test_set_treat_images_as_undeclared_write_in/test_set_treat_images_as_undeclared_write_in_config.json similarity index 96% rename from src/test/resources/network/brightspots/rcv/test_data/test_set_treat_blank_as_undeclared_write_in/test_set_treat_blank_as_undeclared_write_in_config.json rename to src/test/resources/network/brightspots/rcv/test_data/test_set_treat_images_as_undeclared_write_in/test_set_treat_images_as_undeclared_write_in_config.json index ed9adedd5..ae87695b5 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/test_set_treat_blank_as_undeclared_write_in/test_set_treat_blank_as_undeclared_write_in_config.json +++ b/src/test/resources/network/brightspots/rcv/test_data/test_set_treat_images_as_undeclared_write_in/test_set_treat_images_as_undeclared_write_in_config.json @@ -11,7 +11,7 @@ "generateCdfJson" : false }, "cvrFileSources" : [ { - "filePath" : "test_set_treat_blank_as_undeclared_write_in_cvr.xlsx", + "filePath" : "test_set_treat_images_as_undeclared_write_in_cvr.xlsx", "contestId" : "", "firstVoteColumnIndex" : "2", "firstVoteRowIndex" : "2", diff --git a/src/test/resources/network/brightspots/rcv/test_data/test_set_treat_images_as_undeclared_write_in/test_set_treat_images_as_undeclared_write_in_cvr.xlsx b/src/test/resources/network/brightspots/rcv/test_data/test_set_treat_images_as_undeclared_write_in/test_set_treat_images_as_undeclared_write_in_cvr.xlsx new file mode 100644 index 000000000..3c38b3645 Binary files /dev/null and b/src/test/resources/network/brightspots/rcv/test_data/test_set_treat_images_as_undeclared_write_in/test_set_treat_images_as_undeclared_write_in_cvr.xlsx differ diff --git a/src/test/resources/network/brightspots/rcv/test_data/test_set_treat_blank_as_undeclared_write_in/test_set_treat_blank_as_undeclared_write_in_expected_detailed_report.csv b/src/test/resources/network/brightspots/rcv/test_data/test_set_treat_images_as_undeclared_write_in/test_set_treat_images_as_undeclared_write_in_expected_detailed_report.csv similarity index 100% rename from src/test/resources/network/brightspots/rcv/test_data/test_set_treat_blank_as_undeclared_write_in/test_set_treat_blank_as_undeclared_write_in_expected_detailed_report.csv rename to src/test/resources/network/brightspots/rcv/test_data/test_set_treat_images_as_undeclared_write_in/test_set_treat_images_as_undeclared_write_in_expected_detailed_report.csv diff --git a/src/test/resources/network/brightspots/rcv/test_data/test_set_treat_blank_as_undeclared_write_in/test_set_treat_blank_as_undeclared_write_in_expected_detailed_report.json b/src/test/resources/network/brightspots/rcv/test_data/test_set_treat_images_as_undeclared_write_in/test_set_treat_images_as_undeclared_write_in_expected_detailed_report.json similarity index 100% rename from src/test/resources/network/brightspots/rcv/test_data/test_set_treat_blank_as_undeclared_write_in/test_set_treat_blank_as_undeclared_write_in_expected_detailed_report.json rename to src/test/resources/network/brightspots/rcv/test_data/test_set_treat_images_as_undeclared_write_in/test_set_treat_images_as_undeclared_write_in_expected_detailed_report.json diff --git a/src/test/resources/network/brightspots/rcv/test_data/tiebreak_generate_permutation_test/tiebreak_generate_permutation_test_cvr.xlsx b/src/test/resources/network/brightspots/rcv/test_data/tiebreak_generate_permutation_test/tiebreak_generate_permutation_test_cvr.xlsx index 2f56c7a03..6cc0afa13 100644 Binary files a/src/test/resources/network/brightspots/rcv/test_data/tiebreak_generate_permutation_test/tiebreak_generate_permutation_test_cvr.xlsx and b/src/test/resources/network/brightspots/rcv/test_data/tiebreak_generate_permutation_test/tiebreak_generate_permutation_test_cvr.xlsx differ diff --git a/src/test/resources/network/brightspots/rcv/test_data/tiebreak_previous_round_counts_then_random_test/tiebreak_previous_round_counts_then_random_test_config.json b/src/test/resources/network/brightspots/rcv/test_data/tiebreak_previous_round_counts_then_random_test/tiebreak_previous_round_counts_then_random_test_config.json index 52dc89138..fd14860c1 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/tiebreak_previous_round_counts_then_random_test/tiebreak_previous_round_counts_then_random_test_config.json +++ b/src/test/resources/network/brightspots/rcv/test_data/tiebreak_previous_round_counts_then_random_test/tiebreak_previous_round_counts_then_random_test_config.json @@ -52,7 +52,7 @@ "decimalPlacesForVoteArithmetic" : "4", "minimumVoteThreshold" : "0", "maxSkippedRanksAllowed" : "0", - "maxRankingsAllowed" : "3", + "maxRankingsAllowed" : "2", "nonIntegerWinningThreshold" : false, "doesFirstRoundDetermineThreshold" : false, "hareQuota" : false, diff --git a/src/test/resources/network/brightspots/rcv/test_data/tiebreak_previous_round_counts_then_random_test/tiebreak_previous_round_counts_then_random_test_cvr.xlsx b/src/test/resources/network/brightspots/rcv/test_data/tiebreak_previous_round_counts_then_random_test/tiebreak_previous_round_counts_then_random_test_cvr.xlsx index 71f41d623..161ff19be 100644 Binary files a/src/test/resources/network/brightspots/rcv/test_data/tiebreak_previous_round_counts_then_random_test/tiebreak_previous_round_counts_then_random_test_cvr.xlsx and b/src/test/resources/network/brightspots/rcv/test_data/tiebreak_previous_round_counts_then_random_test/tiebreak_previous_round_counts_then_random_test_cvr.xlsx differ diff --git a/src/test/resources/network/brightspots/rcv/test_data/tiebreak_seed_test/tiebreak_seed_test_config.json b/src/test/resources/network/brightspots/rcv/test_data/tiebreak_seed_test/tiebreak_seed_test_config.json index 70daad4cf..60d3e7d78 100644 --- a/src/test/resources/network/brightspots/rcv/test_data/tiebreak_seed_test/tiebreak_seed_test_config.json +++ b/src/test/resources/network/brightspots/rcv/test_data/tiebreak_seed_test/tiebreak_seed_test_config.json @@ -48,7 +48,7 @@ "decimalPlacesForVoteArithmetic" : "4", "minimumVoteThreshold" : "0", "maxSkippedRanksAllowed" : "0", - "maxRankingsAllowed" : "3", + "maxRankingsAllowed" : "1", "nonIntegerWinningThreshold" : false, "doesFirstRoundDetermineThreshold" : false, "hareQuota" : false,