diff --git a/client/src/component/AuditReportForm.tsx b/client/src/component/AuditReportForm.tsx index 48b142f0..845865ec 100644 --- a/client/src/component/AuditReportForm.tsx +++ b/client/src/component/AuditReportForm.tsx @@ -31,6 +31,7 @@ const REPORT_TYPES: ReportType[] = [ {key:'ActivityReport', label:'Activity Report'}, {key:'StateReport', label:'State Report'}, {key:'JSON', label:'Json Reports'}, + {key:'summarize_IRV', label:'IRV summaries'}, {key:'ranked_ballot_interpretation', label:'Ranked vote interpretation'} ]; @@ -75,38 +76,42 @@ class AuditReportForm extends React.Component { return ( - -
+ enforceFocus={false}> + + +
Audit reports
- - {REPORT_TYPES.map(ty => { - let key = ty.key; - let label = ty.label; - return
- }) - } + {REPORT_TYPES.map(ty => { + let key = ty.key; + let label = ty.label; + return
+
+ }) + }
-
- - + -
-
- +
+ +
); } diff --git a/client/src/component/DOS/Dashboard/Round/Control.tsx b/client/src/component/DOS/Dashboard/Round/Control.tsx index 011885f7..344e314c 100644 --- a/client/src/component/DOS/Dashboard/Round/Control.tsx +++ b/client/src/component/DOS/Dashboard/Round/Control.tsx @@ -3,6 +3,8 @@ import * as React from 'react'; import { Button, Card, Elevation, Intent, ProgressBar } from '@blueprintjs/core'; import startNextRound from 'corla/action/dos/startNextRound'; import AuditReportForm from 'corla/component/AuditReportForm'; +import exportAssertionsAsJson from "corla/action/dos/exportAssertionsAsJson"; +import exportAssertionsAsCsv from "corla/action/dos/exportAssertionsAsCsv"; interface ControlProps { canRenderReport: boolean; @@ -39,17 +41,28 @@ class Control extends React.Component {

Round {currentRound} completed

+ +
+
+ +
- {canRenderReport && ( - )} -
+
+ {canRenderReport && ( + )} +
+ ); }; diff --git a/client/src/component/DOS/Dashboard/Round/Status.tsx b/client/src/component/DOS/Dashboard/Round/Status.tsx index 3cb9279e..866c4990 100644 --- a/client/src/component/DOS/Dashboard/Round/Status.tsx +++ b/client/src/component/DOS/Dashboard/Round/Status.tsx @@ -1,5 +1,8 @@ import * as React from 'react'; import AuditReportForm from 'corla/component/AuditReportForm'; +import {Button, Intent} from "@blueprintjs/core"; +import exportAssertionsAsJson from "corla/action/dos/exportAssertionsAsJson"; +import exportAssertionsAsCsv from "corla/action/dos/exportAssertionsAsCsv"; interface StatusProps { auditIsComplete: boolean; @@ -28,6 +31,16 @@ const Status = (props: StatusProps) => { have finished this round. +
+ +
+
+ +
{canRenderReport && ( getAssertions(){ return ImmutableList.builder().addAll(assertions).build(); } + /** + * Return the overall margin, which is the minimum margin of all the assertions. + * @return the minimum assertion margin. + */ + public int getMinMargin() { + return Collections.min(assertions.stream().map(Assertion::getMargin).toList()); + } + /** * This method checks whether this IRVComparisonAudit's list of assertions has been * appropriately initialised, and throws a RunTimeException if not. It takes a string identifying diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/model/assertion/Assertion.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/model/assertion/Assertion.java index 26c5ac97..f64a4a8f 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/model/assertion/Assertion.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/model/assertion/Assertion.java @@ -258,6 +258,11 @@ public BigDecimal getDilutedMargin(){ return dilutedMargin; } + /** + * Get the assertion's absolute margin. + */ + public int getMargin() { return margin; } + /** * Get an (unmodifiable) map of the assertion's CVR ID-discrepancy records. This is used * for testing purposes. diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/model/vote/IRVBallotInterpretation.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/model/vote/IRVBallotInterpretation.java index 39a8a67a..5c81a238 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/model/vote/IRVBallotInterpretation.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/model/vote/IRVBallotInterpretation.java @@ -34,7 +34,6 @@ public class IRVBallotInterpretation implements PersistentEntity { @Version private Long version; - /** * The contest to which the interpreted vote belongs. */ diff --git a/server/eclipse-project/src/main/java/us/freeandfair/corla/query/ExportQueries.java b/server/eclipse-project/src/main/java/us/freeandfair/corla/query/ExportQueries.java index c1b3ee8c..93393318 100644 --- a/server/eclipse-project/src/main/java/us/freeandfair/corla/query/ExportQueries.java +++ b/server/eclipse-project/src/main/java/us/freeandfair/corla/query/ExportQueries.java @@ -6,15 +6,19 @@ import java.io.InputStreamReader; import java.io.BufferedReader; import java.io.IOException; +import java.math.BigDecimal; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.ArrayList; -import java.util.Map; -import java.util.HashMap; +import java.util.*; +import java.util.stream.Collectors; import java.util.stream.Stream; +import au.org.democracydevelopers.corla.endpoint.AbstractAllIrvEndpoint; +import au.org.democracydevelopers.corla.model.ContestType; +import au.org.democracydevelopers.corla.model.GenerateAssertionsSummary; +import au.org.democracydevelopers.corla.model.IRVComparisonAudit; +import au.org.democracydevelopers.corla.query.GenerateAssertionsSummaryQueries; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; @@ -29,6 +33,9 @@ import org.hibernate.query.Query; +import us.freeandfair.corla.model.ComparisonAudit; +import us.freeandfair.corla.model.Contest; +import us.freeandfair.corla.model.ContestResult; import us.freeandfair.corla.persistence.Persistence; /** export queries **/ @@ -120,6 +127,10 @@ public static void customOut(final String query, final OutputStream os) { **/ public static void jsonOut(final String query, final OutputStream os) { final Session s = Persistence.currentSession(); + + // Make sure the contest_result table has the right info for IRV. + updateIRVContestResults(s); + final String withoutSemi = query.replace(";", ""); final String jsonQuery = String.format("SELECT cast(row_to_json(r) as text)" + " FROM (%s) r", withoutSemi); @@ -153,6 +164,10 @@ public static void jsonOut(final String query, final OutputStream os) { /** send query results to output stream as csv **/ public static void csvOut(final String query, final OutputStream os) { final Session s = Persistence.currentSession(); + + // Make sure the contest_result table has the right info for IRV. + updateIRVContestResults(s); + final String withoutSemi = query.replace(";", ""); s.doWork(new CSVWork(withoutSemi, os)); } @@ -168,7 +183,7 @@ public static List getSqlFolderFiles() { final String[] fileNames = {"batch_count_comparison.sql", "contest.sql", "contest_comparison.sql", "contest_selection.sql", "contests_by_county.sql", "tabulate.sql", "tabulate_county.sql", "upload_status.sql", "seed.sql", - "ranked_ballot_interpretation.sql"}; + "summarize_IRV.sql", "ranked_ballot_interpretation.sql"}; for (final String f : fileNames) { paths.add(String.format("%s/%s", folder, f)); } @@ -269,4 +284,63 @@ public static long custCopyOut(final String sql, OutputStream to, CopyManager cm } } + /** + * This function deals, somewhat inelegantly, with the problem that the ContestResult data structure + * used in most queries does not have correct values for things like winners, losers, margin, and + * diluted margin for IRV contests. This function sets them manually from the + * GenerateAssertionsSummary table, then flushes the database so that the csv reports, which are + * based on database queries, get the right values from the contest_result table. + * @param s The current Hibernate session. + */ + private static void updateIRVContestResults(final Session s) { + final String prefix = "[updateIRVContestResults]"; + LOGGER.debug(String.format("%s %s.", prefix, + "Updating IRV contest results from generate assertions summary")); + + final List comparisonAudits = ComparisonAuditQueries.sortedList(); + + for(final ComparisonAudit ca : comparisonAudits) { + if(ca instanceof IRVComparisonAudit) { + final Set choices = new HashSet<>(); + // Get the choices for the contest. These should be the same for all the contests, but + // gather the whole set from all of them just in case. + for(final Contest contest : ca.contestResult().getContests()) { + if (contest.description().equals(ContestType.IRV.toString())) { + contest.choices().stream().map(ch -> choices.add(ch.name())); + } else { + // We have an IRVComparisonAudit for a not-IRV contest. Definitely not supposed to happen. + final String msg = "IRV-type Comparison Audit encountered for non-IRV contest"; + LOGGER.error(String.format("%s %s %s", prefix, msg, contest.name())); + throw new RuntimeException(msg+" "+contest.name()); + } + } + + final ContestResult contestResult = ca.contestResult(); + + // Use the choices and the summary to update the contest result in the database. + final Optional summary + = GenerateAssertionsSummaryQueries.matching(ca.getContestName()); + if(summary.isPresent()) { + final String winner = summary.get().getWinner(); + contestResult.setWinners(Set.of(winner)); + choices.remove(winner); + contestResult.setLosers(choices); + contestResult.setMinMargin(((IRVComparisonAudit) ca).getMinMargin()); + contestResult.setDilutedMargin(ca.getDilutedMargin()); + } else { + // If no summary is present, just set the winner to be blank, the losers to be everyone, + // and the margins to be zero. + LOGGER.debug(String.format("%s %s %s", prefix, "Couldn't find summary for IRV contest", + ca.getContestName())); + contestResult.setWinners(Set.of()); + contestResult.setLosers(choices); + contestResult.setMinMargin(0); + contestResult.setDilutedMargin(BigDecimal.ZERO); + } + } + } + + s.flush(); + + } } diff --git a/server/eclipse-project/src/main/java/us/freeandfair/corla/report/CountyReport.java b/server/eclipse-project/src/main/java/us/freeandfair/corla/report/CountyReport.java index 59d53e3e..bbb8c838 100644 --- a/server/eclipse-project/src/main/java/us/freeandfair/corla/report/CountyReport.java +++ b/server/eclipse-project/src/main/java/us/freeandfair/corla/report/CountyReport.java @@ -15,7 +15,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.math.BigDecimal; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; @@ -27,7 +26,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.OptionalInt; import java.util.TimeZone; import org.apache.poi.ss.usermodel.BorderStyle; @@ -546,76 +544,15 @@ public Workbook generateExcelWorkbook() { max_cell_number = Math.max(max_cell_number, cell_number); row_number = row_number - 1; // don't skip a line for the first contest for (final CountyContestResult ccr : my_driving_contest_results) { - row_number++; - row = summary_sheet.createRow(row_number++); - cell_number = 0; - cell = row.createCell(cell_number++); - cell.setCellStyle(bold_style); - cell.setCellValue(ccr.contest().name() + " - Vote For " + ccr.contest().votesAllowed()); - - cell = row.createCell(cell_number++); - cell.setCellStyle(bold_style); - cell.setCellValue("Choice"); - - cell = row.createCell(cell_number++); - cell.setCellStyle(bold_right_style); - cell.setCellValue("W/L"); - cell = row.createCell(cell_number++); - cell.setCellStyle(bold_right_style); - cell.setCellValue("Votes"); - - cell = row.createCell(cell_number++); - cell.setCellStyle(bold_right_style); - cell.setCellValue("Margin"); - - cell = row.createCell(cell_number++); - cell.setCellStyle(bold_right_style); - cell.setCellValue("Diluted Margin %"); - - for (final String choice : ccr.rankedChoices()) { - row = summary_sheet.createRow(row_number++); - max_cell_number = Math.max(max_cell_number, cell_number); - cell_number = 1; - cell = row.createCell(cell_number++); - cell.setCellStyle(standard_style); - cell.setCellValue(choice); + StateReport.CellStatus status = StateReport.makeContestSummary(row_number, max_cell_number, ccr, summary_sheet, + bold_style, integer_style, bold_right_style, decimal_style, + standard_style, standard_right_style); - cell = row.createCell(cell_number++); - cell.setCellStyle(standard_right_style); - if ((ccr.winners().stream().anyMatch(w -> w.equalsIgnoreCase(choice)))) { - cell.setCellValue("W"); - } else { - cell.setCellValue("L"); - } - - cell = row.createCell(cell_number++); - cell.setCellStyle(integer_style); - cell.setCellType(CellType.NUMERIC); - cell.setCellValue(ccr.voteTotals().get(choice)); - - if (ccr.winners().contains(choice)) { - cell = row.createCell(cell_number++); - cell.setCellStyle(integer_style); - cell.setCellType(CellType.NUMERIC); - final OptionalInt margin = ccr.marginToNearestLoser(choice); - if (margin.isPresent()) { - cell.setCellValue(margin.getAsInt()); - } - - cell = row.createCell(cell_number++); - cell.setCellStyle(decimal_style); - cell.setCellType(CellType.NUMERIC); - final BigDecimal diluted_margin = ccr.countyDilutedMarginToNearestLoser(choice); - if (diluted_margin != null) { - cell.setCellValue(diluted_margin.doubleValue() * 100); - } - } - } + row_number = status.row_number(); + max_cell_number = status.max_cell_number(); } - row_number++; - summary_sheet.getPrintSetup().setLandscape(true); summary_sheet.getPrintSetup().setFitWidth((short) 1); for (int i = 0; i < max_cell_number; i++) { diff --git a/server/eclipse-project/src/main/java/us/freeandfair/corla/report/ReportRows.java b/server/eclipse-project/src/main/java/us/freeandfair/corla/report/ReportRows.java index e7b35036..8a32811b 100644 --- a/server/eclipse-project/src/main/java/us/freeandfair/corla/report/ReportRows.java +++ b/server/eclipse-project/src/main/java/us/freeandfair/corla/report/ReportRows.java @@ -26,7 +26,6 @@ import org.apache.log4j.Logger; import us.freeandfair.corla.controller.ContestCounter; -import us.freeandfair.corla.math.Audit; import us.freeandfair.corla.model.CastVoteRecord; import us.freeandfair.corla.model.CVRAuditInfo; import us.freeandfair.corla.model.CVRContestInfo; @@ -40,8 +39,6 @@ import us.freeandfair.corla.util.SuppressFBWarnings; -import static au.org.democracydevelopers.corla.endpoint.GenerateAssertions.UNKNOWN_WINNER; - /** * Contains the query-ing and processing of two report types: * activity and results @@ -388,6 +385,7 @@ public static List> genSumResultsReport() { } row.put("Winner", toString(ca.contestResult().getWinners().iterator().next())); } + // All this data makes sense for both IRV and plurality. row.put("Risk Limit met?", yesNo(riskLimitMet(ca.getRiskLimit(), riskMsmnt))); row.put("Risk measurement %", sigFig(percentage(riskMsmnt), 1).toString()); @@ -399,13 +397,18 @@ public static List> genSumResultsReport() { row.put("disc -2", toString(ca.discrepancyCount(-2))); row.put("gamma", toString(ca.getGamma())); row.put("audited sample count", toString(ca.getAuditedSampleCount())); - - // very detailed extra info + // For IRV, this is the total number of mentions among all valid interpretations. row.put("ballot count", toString(ca.contestResult().getBallotCount())); - row.put("min margin", toString(ca.contestResult().getMinMargin())); - // These totals make sense only for plurality. - if(! (ca instanceof IRVComparisonAudit)) { + if(ca instanceof IRVComparisonAudit) { + // For IRV, get the min margin direct from the IRVComparisonAudit. + row.put("min margin", toString(((IRVComparisonAudit) ca).getMinMargin())); + + } else { + // For plurality, get the min margin from the contestResult. + row.put("min margin", toString(ca.contestResult().getMinMargin())); + + // These totals make sense only for plurality. Omit entirely for IRV. final List> rankedTotals = ContestCounter.rankTotals(ca.contestResult().getVoteTotals()); diff --git a/server/eclipse-project/src/main/java/us/freeandfair/corla/report/StateReport.java b/server/eclipse-project/src/main/java/us/freeandfair/corla/report/StateReport.java index 982ec2f7..33595e9b 100644 --- a/server/eclipse-project/src/main/java/us/freeandfair/corla/report/StateReport.java +++ b/server/eclipse-project/src/main/java/us/freeandfair/corla/report/StateReport.java @@ -32,6 +32,7 @@ import java.util.TimeZone; import java.util.TreeMap; +import au.org.democracydevelopers.corla.model.ContestType; import org.apache.poi.ss.usermodel.BorderStyle; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; @@ -479,76 +480,17 @@ public Workbook generateExcelWorkbook() { cell.setCellValue(e.getKey().name() + " County - Audited Contests"); row_number = row_number - 1; // don't skip a line before first contest - - for (final CountyContestResult ccr : e.getValue().drivingContestResults()) { - row_number++; - row = summary_sheet.createRow(row_number++); - cell_number = 0; - - cell = row.createCell(cell_number++); - cell.setCellStyle(bold_style); - cell.setCellValue(ccr.contest().name() + " - Vote For " + - ccr.contest().votesAllowed()); - - cell = row.createCell(cell_number++); - cell.setCellStyle(bold_style); - cell.setCellValue("Choice"); - cell = row.createCell(cell_number++); - cell.setCellStyle(bold_right_style); - cell.setCellValue("W/L"); - - cell = row.createCell(cell_number++); - cell.setCellStyle(bold_right_style); - cell.setCellValue("Votes"); - - cell = row.createCell(cell_number++); - cell.setCellStyle(bold_right_style); - cell.setCellValue("Margin"); - - cell = row.createCell(cell_number++); - cell.setCellStyle(bold_right_style); - cell.setCellValue("Diluted Margin %"); - - for (final String choice : ccr.rankedChoices()) { - row = summary_sheet.createRow(row_number++); - max_cell_number = Math.max(max_cell_number, cell_number); - cell_number = 1; - cell = row.createCell(cell_number++); - cell.setCellStyle(standard_style); - cell.setCellValue(choice); + // County-level results really don't make sense for IRV. Therefore print a + // very brief summary and a reference to the assertions csv. + for (final CountyContestResult ccr : e.getValue().drivingContestResults()) { - cell = row.createCell(cell_number++); - cell.setCellStyle(standard_right_style); - if ((ccr.winners().stream().anyMatch(w -> w.equalsIgnoreCase(choice)))) { - cell.setCellValue("W"); - } else { - cell.setCellValue("L"); - } + final CellStatus status = makeContestSummary(row_number, max_cell_number, ccr, summary_sheet, + bold_style, integer_style, bold_right_style, decimal_style, + standard_style, standard_right_style); - cell = row.createCell(cell_number++); - cell.setCellStyle(integer_style); - cell.setCellType(CellType.NUMERIC); - cell.setCellValue(ccr.voteTotals().get(choice)); - - if (ccr.winners().contains(choice)) { - cell = row.createCell(cell_number++); - cell.setCellStyle(integer_style); - cell.setCellType(CellType.NUMERIC); - final OptionalInt margin = ccr.marginToNearestLoser(choice); - if (margin.isPresent()) { - cell.setCellValue(margin.getAsInt()); - } - - cell = row.createCell(cell_number++); - cell.setCellStyle(decimal_style); - cell.setCellType(CellType.NUMERIC); - final BigDecimal diluted_margin = ccr.countyDilutedMarginToNearestLoser(choice); - if (diluted_margin != null) { - cell.setCellValue(diluted_margin.doubleValue() * 100); - } - } - } + row_number = status.row_number; + max_cell_number = status.max_cell_number; } } } @@ -739,7 +681,125 @@ public Workbook generateExcelWorkbook() { return workbook; } - + + /** + * Make the cells for one contest's worth of summary data. For IRV, this just prints the + * contest name, choices, and the fact that it's IRV. For plurality, it outputs much more + * detailed data. + * @param row_number Number of the current row, which is incremented as we progress and + * included in Cell Status on return. + * @param max_cell_number The maximum cell number used, which is included in Cell Status on return. + * @param ccr The County Contest Result to be summarized. + * @param summary_sheet The sheet to be added to. + * @param bold_style All these styles are included (though they could be class-level finals) + * in case a caller from another class wants different styles. + * @param integer_style " + * @param bold_right_style " + * @param decimal_style " + * @param standard_style " + * @param standard_right_style " + * @return a CellStatus record, which tells the ensuing generation function which row it is up to + * and what maximum cell number has been used. + */ + protected static CellStatus makeContestSummary(int row_number, int max_cell_number, final CountyContestResult ccr, + final Sheet summary_sheet, final CellStyle bold_style, final CellStyle integer_style, final CellStyle bold_right_style, + final CellStyle decimal_style, final CellStyle standard_style, final CellStyle standard_right_style) { + row_number++; + Row row = summary_sheet.createRow(row_number++); + int cell_number = 0; + + Cell cell = row.createCell(cell_number++); + cell.setCellStyle(bold_style); + + final boolean isIRV = ccr.contest().description().equals(ContestType.IRV.toString()); + + if (isIRV) { + cell.setCellValue(ccr.contest().name() + " - IRV"); + } else { + cell.setCellValue(ccr.contest().name() + " - Vote For " + ccr.contest().votesAllowed()); + } + + // Keep the choices column, for both IRV and plurality. + cell = row.createCell(cell_number++); + cell.setCellStyle(bold_style); + cell.setCellValue("Choice"); + + if (isIRV) { + // If it's IRV, the summary data is in the assertions csv. Add a message. + cell = row.createCell(cell_number++); + cell.setCellStyle(standard_style); + cell.setCellValue("See assertions csv"); + } else { + // Plurality. Print the other data - winners, vote tallies, margins. + cell = row.createCell(cell_number++); + cell.setCellStyle(bold_right_style); + cell.setCellValue("W/L"); + + cell = row.createCell(cell_number++); + cell.setCellStyle(bold_right_style); + cell.setCellValue("Votes"); + + cell = row.createCell(cell_number++); + cell.setCellStyle(bold_right_style); + cell.setCellValue("Margin"); + + cell = row.createCell(cell_number++); + cell.setCellStyle(bold_right_style); + cell.setCellValue("Diluted Margin %"); + } + + for (final String choice : ccr.rankedChoices()) { + // List all the choices, for both IRV and plurality. + row = summary_sheet.createRow(row_number++); + max_cell_number = Math.max(max_cell_number, cell_number); + + cell_number = 1; + cell = row.createCell(cell_number++); + cell.setCellStyle(standard_style); + cell.setCellValue(choice); + + if (!isIRV) { + // Print the other data - winners, vote tallies, margins - only for plurality, not IRV. + cell = row.createCell(cell_number++); + cell.setCellStyle(standard_right_style); + if ((ccr.winners().stream().anyMatch(w -> w.equalsIgnoreCase(choice)))) { + cell.setCellValue("W"); + } else { + cell.setCellValue("L"); + } + + cell = row.createCell(cell_number++); + cell.setCellStyle(integer_style); + cell.setCellType(CellType.NUMERIC); + cell.setCellValue(ccr.voteTotals().get(choice)); + + if (ccr.winners().contains(choice)) { + cell = row.createCell(cell_number++); + cell.setCellStyle(integer_style); + cell.setCellType(CellType.NUMERIC); + final OptionalInt margin = ccr.marginToNearestLoser(choice); + if (margin.isPresent()) { + cell.setCellValue(margin.getAsInt()); + } + + cell = row.createCell(cell_number++); + cell.setCellStyle(decimal_style); + cell.setCellType(CellType.NUMERIC); + final BigDecimal diluted_margin = ccr.countyDilutedMarginToNearestLoser(choice); + if (diluted_margin != null) { + cell.setCellValue(diluted_margin.doubleValue() * 100); + } + } + } + } + return new CellStatus(row_number, max_cell_number); + } + + /** Just a way of making a pair so we can thread the current row number + * and the max cell number through the for loop. + */ + public record CellStatus(int row_number, int max_cell_number) {}; + /** * @return the PDF representation of this report, as a byte array. */ diff --git a/server/eclipse-project/src/main/resources/sql/contest.sql b/server/eclipse-project/src/main/resources/sql/contest.sql index ff00ef19..1a7021fb 100644 --- a/server/eclipse-project/src/main/resources/sql/contest.sql +++ b/server/eclipse-project/src/main/resources/sql/contest.sql @@ -10,6 +10,7 @@ SELECT cr.winners_allowed, cr.ballot_count AS ballot_card_count, agg.contest_ballot_card_count, + -- Note these will only be correct for IRV after a run of ExportQueries::updateIRVContestResults. SUBSTRING(cr.winners, 2, LENGTH(cr.winners) - 2) AS winners, cr.min_margin, ca.risk_limit, diff --git a/server/eclipse-project/src/main/resources/sql/contest_comparison.sql b/server/eclipse-project/src/main/resources/sql/contest_comparison.sql index cc36a1c0..1619a376 100644 --- a/server/eclipse-project/src/main/resources/sql/contest_comparison.sql +++ b/server/eclipse-project/src/main/resources/sql/contest_comparison.sql @@ -1,19 +1,19 @@ -- For each contest, and for each cast vote record in the random sequence --- (with duplicates) for which the Audit Board has submitted information, and which +-- (with duplicates) for which the Audit Board has submitted information, and which -- covers the contest in question, -- original cvr info, audit board interp info. -- note that the random sequence index (includes dupes) is contest_audit_info.index -- cvr_contest_info.index is the index of the *contest* on the ballot --- Note that in case of an overvote, `cci_a.choices` shows all the choices the Audit Board thought the voter intended, while cci.choices will *not* show all those choices. +-- Note that in case of an overvote, `cci_a.choices` shows all the choices the Audit Board thought the voter intended, while cci.choices will *not* show all those choices. -- TODO: ensure that "ballot not found" (cast_vote_record.record_type = 'PHANTOM_BALLOT') is properly handled SELECT DISTINCT - cty.name AS county_name, - cn.name AS contest_name, + cty.name AS county_name, + cn.name AS contest_name, cvr_s.imprinted_id, - cvr_s.ballot_type, + cvr_s.ballot_type, SUBSTRING(cci.choices, 2, LENGTH(cci.choices) - 2) AS choice_per_voting_computer, SUBSTRING(cci_a.choices, 2, LENGTH(cci_a.choices) - 2) AS audit_board_selection, cci_a.consensus, diff --git a/server/eclipse-project/src/main/resources/sql/summarize_IRV.sql b/server/eclipse-project/src/main/resources/sql/summarize_IRV.sql new file mode 100644 index 00000000..3852f431 --- /dev/null +++ b/server/eclipse-project/src/main/resources/sql/summarize_IRV.sql @@ -0,0 +1,7 @@ +-- Show the IRV summaries, sorted by contest name. +-- This is usually a (unique) winner and an empty error, but may also be a +-- blank winner and a non-empty error with an explanatory message. + +SELECT gas.contest_name, gas.winner, gas.error, gas.message +FROM generate_assertions_summary as gas +ORDER BY contest_name; diff --git a/server/eclipse-project/src/main/resources/sql/tabulate.sql b/server/eclipse-project/src/main/resources/sql/tabulate.sql index aa9afd3f..116befc2 100644 --- a/server/eclipse-project/src/main/resources/sql/tabulate.sql +++ b/server/eclipse-project/src/main/resources/sql/tabulate.sql @@ -1,14 +1,21 @@ -- Vote Counts by Contest, Choice - -SELECT +-- Updated to include on PLURALITY contests +-- Uses SELECT DISTINCT because a contest_result can have multiple contests (from different counties), +-- of which we only want one. +SELECT DISTINCT contest_result.contest_name AS contest_name, contest_vote_total.choice AS choice, contest_vote_total.vote_total AS votes FROM contest_vote_total, - contest_result + contest_result, + contests_to_contest_results, + contest WHERE - contest_vote_total.result_id = contest_result.id + contest_vote_total.result_id = contest_result.id AND + contest_result.id = contests_to_contest_results.contest_result_id AND + contests_to_contest_results.contest_id = contest.id AND + contest.description = 'PLURALITY' ORDER BY contest_result.contest_name ASC, contest_vote_total.vote_total DESC; diff --git a/server/eclipse-project/src/main/resources/sql/tabulate_county.sql b/server/eclipse-project/src/main/resources/sql/tabulate_county.sql index a347f5c0..c574380e 100644 --- a/server/eclipse-project/src/main/resources/sql/tabulate_county.sql +++ b/server/eclipse-project/src/main/resources/sql/tabulate_county.sql @@ -13,7 +13,8 @@ FROM WHERE county_contest_vote_total.result_id = county_contest_result.id AND county_contest_result.contest_id = contest.id AND - county_contest_result.county_id = county.id + county_contest_result.county_id = county.id AND + contest.description = 'PLURALITY' ORDER BY county.name ASC, contest.id ASC,