From 84c48c3eff6cbeca8db867c26fb0967a6137070f Mon Sep 17 00:00:00 2001 From: vteague Date: Wed, 7 Aug 2024 20:49:24 +1000 Subject: [PATCH 01/20] Add sql to make a report that outputs the IRV summaries. --- client/src/component/AuditReportForm.tsx | 1 + .../java/us/freeandfair/corla/query/ExportQueries.java | 2 +- .../src/main/resources/sql/summarize_IRV.sql | 7 +++++++ 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 server/eclipse-project/src/main/resources/sql/summarize_IRV.sql diff --git a/client/src/component/AuditReportForm.tsx b/client/src/component/AuditReportForm.tsx index 48b142f0..0741a71a 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'} ]; 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 4d2ad2d3..bdf1ae53 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 @@ -172,7 +172,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)); } 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; From ec72c5091e355c595c4b97c5aac6d9b67815bc90 Mon Sep 17 00:00:00 2001 From: vteague Date: Thu, 8 Aug 2024 10:07:53 +1000 Subject: [PATCH 02/20] tabulate.sql updated to include only PLURALITY contests. --- .../src/main/resources/sql/tabulate.sql | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) 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; From 8bae62d621eb7a49b83961ba2fb05d4839deee21 Mon Sep 17 00:00:00 2001 From: vteague Date: Thu, 8 Aug 2024 11:42:09 +1000 Subject: [PATCH 03/20] Updated contest_comparison.sql to include raw choices for uploaded ballots. --- .../corla/model/vote/IRVBallotInterpretation.java | 1 - .../src/main/resources/sql/contest_comparison.sql | 13 ++++++++++++- .../src/main/resources/sql/tabulate_county.sql | 3 ++- 3 files changed, 14 insertions(+), 3 deletions(-) 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/resources/sql/contest_comparison.sql b/server/eclipse-project/src/main/resources/sql/contest_comparison.sql index cc36a1c0..2d57963a 100644 --- a/server/eclipse-project/src/main/resources/sql/contest_comparison.sql +++ b/server/eclipse-project/src/main/resources/sql/contest_comparison.sql @@ -21,7 +21,8 @@ SELECT DISTINCT cci_a.comment AS audit_board_comment, cvr_a.timestamp, cai.cvr_id, - cpa.audit_reason + cpa.audit_reason, + raw_choice_per_voting_computer FROM cvr_audit_info AS cai @@ -47,6 +48,16 @@ FROM LEFT JOIN comparison_audit AS cpa ON cpa.audit_reason = cta.reason and cast (cci.cvr_id as TEXT) = ANY (string_to_array(substring(cpa.contest_cvr_ids from 2 for (char_length(cpa.contest_cvr_ids)-2)), ',')) + LEFT JOIN ( + -- Get matching raw IRV votes for upload ballots (if there are any in irv_ballot_interpretation). + SELECT raw_choices AS raw_choice_per_voting_computer, + contest_id, + imprinted_id + FROM irv_ballot_interpretation + WHERE irv_ballot_interpretation.record_type = 'UPLOADED' + ) as irv_up + ON irv_up.contest_id = cci.contest_id + AND irv_up.imprinted_id = cvr_s.imprinted_id ORDER BY county_name, contest_name ; 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, From 2e6f919bdcbffb19cabc2a73efbd2676967592c9 Mon Sep 17 00:00:00 2001 From: vteague Date: Thu, 8 Aug 2024 11:50:27 +1000 Subject: [PATCH 04/20] Updated contest_comparison.sql to include raw choices for audited ballots. --- .../main/resources/sql/contest_comparison.sql | 85 ++++++++++--------- 1 file changed, 47 insertions(+), 38 deletions(-) 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 2d57963a..cf29c8fc 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, @@ -25,39 +25,48 @@ SELECT DISTINCT raw_choice_per_voting_computer FROM - cvr_audit_info AS cai - LEFT JOIN - cvr_contest_info AS cci - ON cci.cvr_id = cai.cvr_id - LEFT JOIN cast_vote_record AS cvr_s - ON cai.cvr_id = cvr_s.id - LEFT JOIN cvr_contest_info AS cci_a - ON cai.acvr_id = cci_a.cvr_id - AND cci_a.contest_id = cci.contest_id - LEFT JOIN - cast_vote_record AS cvr_a - ON cai.acvr_id = cvr_a.id - LEFT JOIN - contest AS cn - ON cci.contest_id = cn.id - LEFT JOIN county AS cty - ON cn.county_id = cty.id - LEFT JOIN - contest_to_audit AS cta - ON (cci.contest_id = cta.contest_id or cn.name = (select cn1.name from contest cn1 where cn1.id=cta.contest_id)) - LEFT JOIN - comparison_audit AS cpa - ON cpa.audit_reason = cta.reason and cast (cci.cvr_id as TEXT) = ANY (string_to_array(substring(cpa.contest_cvr_ids from 2 for (char_length(cpa.contest_cvr_ids)-2)), ',')) - LEFT JOIN ( - -- Get matching raw IRV votes for upload ballots (if there are any in irv_ballot_interpretation). - SELECT raw_choices AS raw_choice_per_voting_computer, - contest_id, - imprinted_id - FROM irv_ballot_interpretation - WHERE irv_ballot_interpretation.record_type = 'UPLOADED' - ) as irv_up - ON irv_up.contest_id = cci.contest_id - AND irv_up.imprinted_id = cvr_s.imprinted_id + cvr_audit_info AS cai + LEFT JOIN + cvr_contest_info AS cci + ON cci.cvr_id = cai.cvr_id + LEFT JOIN cast_vote_record AS cvr_s + ON cai.cvr_id = cvr_s.id + LEFT JOIN cvr_contest_info AS cci_a + ON cai.acvr_id = cci_a.cvr_id + AND cci_a.contest_id = cci.contest_id + LEFT JOIN + cast_vote_record AS cvr_a + ON cai.acvr_id = cvr_a.id + LEFT JOIN + contest AS cn + ON cci.contest_id = cn.id + LEFT JOIN county AS cty + ON cn.county_id = cty.id + LEFT JOIN + contest_to_audit AS cta + ON (cci.contest_id = cta.contest_id or cn.name = (select cn1.name from contest cn1 where cn1.id=cta.contest_id)) + LEFT JOIN + comparison_audit AS cpa + ON cpa.audit_reason = cta.reason and cast (cci.cvr_id as TEXT) = ANY (string_to_array(substring(cpa.contest_cvr_ids from 2 for (char_length(cpa.contest_cvr_ids)-2)), ',')) + LEFT JOIN ( + -- Get matching raw IRV votes for upload ballots (if there are any in irv_ballot_interpretation). + SELECT raw_choices AS raw_choice_per_voting_computer, + contest_id, + imprinted_id + FROM irv_ballot_interpretation + WHERE irv_ballot_interpretation.record_type = 'UPLOADED' + ) as irv_up + ON irv_up.contest_id = cci.contest_id AND irv_up.imprinted_id = cvr_s.imprinted_id + LEFT JOIN ( + -- Get matching raw IRV votes for audit ballots (if there are any in irv_ballot_interpretation). + -- Note that reaudited ballots won't show up here - only the most recent audit record. + SELECT raw_choices AS raw_choice_per_voting_computer, + contest_id, + imprinted_id + FROM irv_ballot_interpretation + WHERE irv_ballot_interpretation.record_type = 'AUDITOR_ENTERED' + ) as irv_audit + ON irv_audit.contest_id = cci.contest_id AND irv_audit.imprinted_id = cvr_s.imprinted_id ORDER BY county_name, contest_name ; From fe1eb345b784ed3001f5bc6185524349370b5e33 Mon Sep 17 00:00:00 2001 From: vteague Date: Thu, 8 Aug 2024 12:04:40 +1000 Subject: [PATCH 05/20] Updated contest_comparison.sql to include raw choices for audited ballots. Included in .csv. --- .../src/main/resources/sql/contest_comparison.sql | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 cf29c8fc..8531c717 100644 --- a/server/eclipse-project/src/main/resources/sql/contest_comparison.sql +++ b/server/eclipse-project/src/main/resources/sql/contest_comparison.sql @@ -22,7 +22,8 @@ SELECT DISTINCT cvr_a.timestamp, cai.cvr_id, cpa.audit_reason, - raw_choice_per_voting_computer + raw_choice_per_voting_computer, + raw_audit_board_selection FROM cvr_audit_info AS cai @@ -60,7 +61,7 @@ FROM LEFT JOIN ( -- Get matching raw IRV votes for audit ballots (if there are any in irv_ballot_interpretation). -- Note that reaudited ballots won't show up here - only the most recent audit record. - SELECT raw_choices AS raw_choice_per_voting_computer, + SELECT raw_choices AS raw_audit_board_selection, contest_id, imprinted_id FROM irv_ballot_interpretation From e7e47e2cff632464c6ed02d2f3d978aae2ff476d Mon Sep 17 00:00:00 2001 From: vteague Date: Sat, 10 Aug 2024 08:50:38 +1000 Subject: [PATCH 06/20] Some notes on what to change for IRV. --- .../src/main/java/us/freeandfair/corla/report/ReportRows.java | 2 ++ .../src/main/java/us/freeandfair/corla/report/StateReport.java | 3 +++ 2 files changed, 5 insertions(+) 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..6ac291fe 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 @@ -463,6 +463,8 @@ public static List> getContestActivity(final String contestName) { } /** build a list of rows for a contest based on tributes **/ + // FIXME VT: Check that this is getting the right data for IRV, particularly the (min) margin, diluted margin, etc. + // used in ResultsReport. public static List> getResultsReport(final String contestName) { final List> rows = new ArrayList(); 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..1562497e 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 @@ -510,6 +510,9 @@ public Workbook generateExcelWorkbook() { cell.setCellStyle(bold_right_style); cell.setCellValue("Diluted Margin %"); + // FIXME VT: This is completely wrong for IRV. Maybe just redo it completely with the overall + // W/L, margin, and diluted margin from the whole contest. + // Also check that the Discrepancies (above) are correct. for (final String choice : ccr.rankedChoices()) { row = summary_sheet.createRow(row_number++); max_cell_number = Math.max(max_cell_number, cell_number); From e2530966bf2de410b77c61543022223d492f8635 Mon Sep 17 00:00:00 2001 From: vteague Date: Sat, 10 Aug 2024 20:08:44 +1000 Subject: [PATCH 07/20] Remove plurality tallying details (winner, margin, diluted margin) from IRV contests in StateReport summary. --- .../corla/report/CountyReport.java | 2 + .../freeandfair/corla/report/StateReport.java | 104 +++++++++++------- 2 files changed, 64 insertions(+), 42 deletions(-) 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..b849c290 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 @@ -217,6 +217,7 @@ public CountyDashboard dashboard() { /** * @return the Excel representation of this report, as a byte array. * @exception IOException if the report cannot be generated. + * FIXME VT: I believe this is never used, except by the county-report endpoint, which is never used. */ public byte[] generateExcel() throws IOException { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -230,6 +231,7 @@ public byte[] generateExcel() throws IOException { /** * @return the Excel workbook for this report. + * FIXME VT: I believe this is never used, except by generateExcel(), which is never used, except via an endpoint that is never used. */ @SuppressWarnings({"checkstyle:magicnumber", "checkstyle:executablestatementcount", "checkstyle:methodlength", "PMD.ExcessiveMethodLength", "PMD.NcssMethodCount", 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 1562497e..15f08f5b 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,95 @@ public Workbook generateExcelWorkbook() { cell.setCellValue(e.getKey().name() + " County - Audited Contests"); row_number = row_number - 1; // don't skip a line before first contest - + + // 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()) { 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()); + 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"); - cell = row.createCell(cell_number++); - cell.setCellStyle(bold_right_style); - cell.setCellValue("W/L"); + 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("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("Margin"); - cell = row.createCell(cell_number++); - cell.setCellStyle(bold_right_style); - cell.setCellValue("Diluted Margin %"); + cell = row.createCell(cell_number++); + cell.setCellStyle(bold_right_style); + cell.setCellValue("Diluted Margin %"); + } - // FIXME VT: This is completely wrong for IRV. Maybe just redo it completely with the overall - // W/L, margin, and diluted margin from the whole contest. - // Also check that the Discrepancies (above) are correct. 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); - 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)) { + if (!isIRV) { + // Print the other data - winners, vote tallies, margins - only for plurality, not IRV. 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.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(decimal_style); + cell.setCellStyle(integer_style); cell.setCellType(CellType.NUMERIC); - final BigDecimal diluted_margin = ccr.countyDilutedMarginToNearestLoser(choice); - if (diluted_margin != null) { - cell.setCellValue(diluted_margin.doubleValue() * 100); + 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); + } } } } From f792c9a8766d608f3d7319c9ac8c091f2d0e3dd8 Mon Sep 17 00:00:00 2001 From: vteague Date: Sat, 10 Aug 2024 20:44:09 +1000 Subject: [PATCH 08/20] Added button to export assertions as CSV and json. --- client/src/component/AuditReportForm.tsx | 51 ++++++++++--------- .../component/DOS/Dashboard/Round/Status.tsx | 13 +++++ 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/client/src/component/AuditReportForm.tsx b/client/src/component/AuditReportForm.tsx index 0741a71a..2709a776 100644 --- a/client/src/component/AuditReportForm.tsx +++ b/client/src/component/AuditReportForm.tsx @@ -76,38 +76,39 @@ 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/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 && ( Date: Sat, 10 Aug 2024 21:11:37 +1000 Subject: [PATCH 09/20] Add format name to zip filename. --- .../corla/endpoint/GetAssertions.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/GetAssertions.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/GetAssertions.java index f419319b..71b5df5c 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/GetAssertions.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/GetAssertions.java @@ -33,7 +33,6 @@ import au.org.democracydevelopers.corla.communication.requestToRaire.GetAssertionsRequest; import au.org.democracydevelopers.corla.communication.responseFromRaire.RaireServiceErrors; -import au.org.democracydevelopers.corla.query.GenerateAssertionsSummaryQueries; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; @@ -51,8 +50,6 @@ import us.freeandfair.corla.model.DoSDashboard; import us.freeandfair.corla.util.SparkHelper; -import static au.org.democracydevelopers.corla.endpoint.GenerateAssertions.UNKNOWN_WINNER; - /** * The Get Assertions endpoint. Takes a GetAssertionsRequest, and an optional format parameter specifying CSV or JSON, * defaulting to json. Returns a zip of all assertions for all IRV contests, in the requested format. @@ -132,11 +129,11 @@ public String endpointBody(final Request the_request, final Response the_respons try { final ZipOutputStream os = new ZipOutputStream(SparkHelper.getRaw(the_response).getOutputStream()); - getAssertions(os, riskLimit, raireUrl, suffix); the_response.header("Content-Type", "application/zip"); - the_response.header("Content-Disposition", "attachment; filename*=UTF-8''assertions.zip"); - + the_response.header("Content-Disposition", + "attachment; filename*=UTF-8''assertions_" + suffix + ".zip"); + getAssertions(os, riskLimit, raireUrl, suffix); ok(the_response); } catch (URISyntaxException | MalformedURLException e) { From 3d59c14ffa79f6018269ce917f0311be89ccf106 Mon Sep 17 00:00:00 2001 From: vteague Date: Sat, 10 Aug 2024 22:17:26 +1000 Subject: [PATCH 10/20] Refactored summary printing to be the same in StateReport and CountyReport. --- .../corla/report/CountyReport.java | 77 +------ .../freeandfair/corla/report/StateReport.java | 191 ++++++++++-------- 2 files changed, 111 insertions(+), 157 deletions(-) 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 b849c290..fa659ec5 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; @@ -217,7 +215,6 @@ public CountyDashboard dashboard() { /** * @return the Excel representation of this report, as a byte array. * @exception IOException if the report cannot be generated. - * FIXME VT: I believe this is never used, except by the county-report endpoint, which is never used. */ public byte[] generateExcel() throws IOException { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -231,7 +228,8 @@ public byte[] generateExcel() throws IOException { /** * @return the Excel workbook for this report. - * FIXME VT: I believe this is never used, except by generateExcel(), which is never used, except via an endpoint that is never used. + * FIXME VT: Refactor the lines that have just been edited in StateReport to make the summaries + * shorter for IRV. */ @SuppressWarnings({"checkstyle:magicnumber", "checkstyle:executablestatementcount", "checkstyle:methodlength", "PMD.ExcessiveMethodLength", "PMD.NcssMethodCount", @@ -548,76 +546,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/StateReport.java b/server/eclipse-project/src/main/java/us/freeandfair/corla/report/StateReport.java index 15f08f5b..efcf7043 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 @@ -484,94 +484,13 @@ public Workbook generateExcelWorkbook() { // 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()) { - row_number++; - row = summary_sheet.createRow(row_number++); - cell_number = 0; - - 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); + 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_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); - } - } - } - } + row_number = status.row_number; + max_cell_number = status.max_cell_number; } } } @@ -762,7 +681,105 @@ public Workbook generateExcelWorkbook() { return workbook; } - + + protected static CellStatus makeContestSummary(int row_number, int max_cell_number, CountyContestResult ccr, Sheet summary_sheet, + CellStyle bold_style, CellStyle integer_style, CellStyle bold_right_style, CellStyle decimal_style, + CellStyle standard_style, 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. */ From ebf983b60fb2f7ef9be3fdbda49475c05634ff3b Mon Sep 17 00:00:00 2001 From: vteague Date: Sun, 11 Aug 2024 20:24:20 +1000 Subject: [PATCH 11/20] Added button to export assertions as CSV and json when round is complete. --- .../component/DOS/Dashboard/Round/Control.tsx | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) 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 && ( + )} +
+ ); }; From e339ff20d7addf6df9b62c3a59261a3525ebc460 Mon Sep 17 00:00:00 2001 From: vteague Date: Sun, 11 Aug 2024 21:41:11 +1000 Subject: [PATCH 12/20] Added 'Download All' button. --- client/src/component/AuditReportForm.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/client/src/component/AuditReportForm.tsx b/client/src/component/AuditReportForm.tsx index 2709a776..845865ec 100644 --- a/client/src/component/AuditReportForm.tsx +++ b/client/src/component/AuditReportForm.tsx @@ -83,7 +83,6 @@ class AuditReportForm extends React.Component {
Audit reports
- {REPORT_TYPES.map(ty => { let key = ty.key; let label = ty.label; @@ -97,14 +96,18 @@ class AuditReportForm extends React.Component { }) } -
- +
+
From 67e58ce8657920e0ca5029ed04b8d5cb96cc9447 Mon Sep 17 00:00:00 2001 From: vteague Date: Tue, 13 Aug 2024 20:04:01 +1000 Subject: [PATCH 13/20] Fix min_margin for IRV in reports. --- .../corla/model/IRVComparisonAudit.java | 10 ++++++++-- .../corla/model/assertion/Assertion.java | 5 +++++ .../freeandfair/corla/report/CountyReport.java | 2 -- .../us/freeandfair/corla/report/ReportRows.java | 17 +++++++++-------- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/model/IRVComparisonAudit.java b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/model/IRVComparisonAudit.java index c5945aee..3e2ff795 100644 --- a/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/model/IRVComparisonAudit.java +++ b/server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/model/IRVComparisonAudit.java @@ -22,7 +22,6 @@ package au.org.democracydevelopers.corla.model; import au.org.democracydevelopers.corla.query.AssertionQueries; -import au.org.democracydevelopers.corla.query.GenerateAssertionsSummaryQueries; import com.google.inject.internal.util.ImmutableList; import java.util.*; @@ -35,7 +34,6 @@ import au.org.democracydevelopers.corla.model.assertion.Assertion; -import static au.org.democracydevelopers.corla.endpoint.GenerateAssertions.UNKNOWN_WINNER; import static java.util.Collections.max; /** @@ -591,6 +589,14 @@ public ImmutableList 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/us/freeandfair/corla/report/CountyReport.java b/server/eclipse-project/src/main/java/us/freeandfair/corla/report/CountyReport.java index fa659ec5..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 @@ -228,8 +228,6 @@ public byte[] generateExcel() throws IOException { /** * @return the Excel workbook for this report. - * FIXME VT: Refactor the lines that have just been edited in StateReport to make the summaries - * shorter for IRV. */ @SuppressWarnings({"checkstyle:magicnumber", "checkstyle:executablestatementcount", "checkstyle:methodlength", "PMD.ExcessiveMethodLength", "PMD.NcssMethodCount", 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 6ac291fe..c6a59168 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 @@ -402,10 +399,16 @@ public static List> genSumResultsReport() { // very detailed extra info 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()); @@ -463,8 +466,6 @@ public static List> getContestActivity(final String contestName) { } /** build a list of rows for a contest based on tributes **/ - // FIXME VT: Check that this is getting the right data for IRV, particularly the (min) margin, diluted margin, etc. - // used in ResultsReport. public static List> getResultsReport(final String contestName) { final List> rows = new ArrayList(); From 0c9dcd00ff3e9c6e6053b4f29625904cd338ba82 Mon Sep 17 00:00:00 2001 From: vteague Date: Thu, 15 Aug 2024 15:32:28 +1000 Subject: [PATCH 14/20] Deriving IRV data from IRVComparisonAudits. --- .../freeandfair/corla/report/StateReport.java | 17 +++++++++++++++++ .../src/main/resources/sql/contest.sql | 1 + 2 files changed, 18 insertions(+) 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 efcf7043..d7c8e552 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 @@ -682,6 +682,23 @@ 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. + * FIXME VT: add descriptions, fix the style thing. + * @param row_number + * @param max_cell_number + * @param ccr + * @param summary_sheet + * @param bold_style + * @param integer_style + * @param bold_right_style + * @param decimal_style + * @param standard_style + * @param standard_right_style + * @return + */ protected static CellStatus makeContestSummary(int row_number, int max_cell_number, CountyContestResult ccr, Sheet summary_sheet, CellStyle bold_style, CellStyle integer_style, CellStyle bold_right_style, CellStyle decimal_style, CellStyle standard_style, CellStyle standard_right_style) { diff --git a/server/eclipse-project/src/main/resources/sql/contest.sql b/server/eclipse-project/src/main/resources/sql/contest.sql index ff00ef19..6a950a67 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, + -- FIXME VT: This will get the wrong values for winners and min_margin for IRV. SUBSTRING(cr.winners, 2, LENGTH(cr.winners) - 2) AS winners, cr.min_margin, ca.risk_limit, From 7102f986a07f05cad228dc2f9d4f9a13f9f28e5d Mon Sep 17 00:00:00 2001 From: vteague Date: Thu, 15 Aug 2024 21:10:28 +1000 Subject: [PATCH 15/20] Store updates to ContestResults. --- .../corla/query/ExportQueries.java | 55 +++++++++++++++++-- .../src/main/resources/sql/contest.sql | 2 +- 2 files changed, 52 insertions(+), 5 deletions(-) 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 602c7ef2..aeaa2965 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 @@ -9,12 +9,15 @@ 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 +32,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 +126,7 @@ 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(); + 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 +160,7 @@ 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(); + updateIRVContestResults(s); final String withoutSemi = query.replace(";", ""); s.doWork(new CSVWork(withoutSemi, os)); } @@ -269,4 +277,43 @@ 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. This 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. + */ + private static void updateIRVContestResults(Session s) { + + List comparisonAudits = ComparisonAuditQueries.sortedList(); + + for(ComparisonAudit ca : comparisonAudits) { + if(ca instanceof IRVComparisonAudit) { + Set choices = new HashSet<>(); + for(Contest contest : ca.contestResult().getContests()) { + if (contest.description().equals(ContestType.IRV.toString())) { + contest.choices().stream().map(ch -> choices.add(ch.name())); + } else { + // FIXME VT: log + throw new RuntimeException(); + } + } + + Optional summary = GenerateAssertionsSummaryQueries.matching(ca.getContestName()); + if(summary.isPresent()) { + ContestResult contestResult = ca.contestResult(); + 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()); + } + } + } + + s.flush(); + + } } diff --git a/server/eclipse-project/src/main/resources/sql/contest.sql b/server/eclipse-project/src/main/resources/sql/contest.sql index 6a950a67..1a7021fb 100644 --- a/server/eclipse-project/src/main/resources/sql/contest.sql +++ b/server/eclipse-project/src/main/resources/sql/contest.sql @@ -10,7 +10,7 @@ SELECT cr.winners_allowed, cr.ballot_count AS ballot_card_count, agg.contest_ballot_card_count, - -- FIXME VT: This will get the wrong values for winners and min_margin for IRV. + -- 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, From d7dbc606189dabfdfc0bc09ee19b9a74fd2541fe Mon Sep 17 00:00:00 2001 From: vteague Date: Fri, 16 Aug 2024 09:53:10 +1000 Subject: [PATCH 16/20] Revert addition of raw votes to contest_comparison.sql. --- .../main/resources/sql/contest_comparison.sql | 69 +++++++------------ 1 file changed, 24 insertions(+), 45 deletions(-) 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 8531c717..1619a376 100644 --- a/server/eclipse-project/src/main/resources/sql/contest_comparison.sql +++ b/server/eclipse-project/src/main/resources/sql/contest_comparison.sql @@ -21,53 +21,32 @@ SELECT DISTINCT cci_a.comment AS audit_board_comment, cvr_a.timestamp, cai.cvr_id, - cpa.audit_reason, - raw_choice_per_voting_computer, - raw_audit_board_selection + cpa.audit_reason FROM - cvr_audit_info AS cai - LEFT JOIN - cvr_contest_info AS cci - ON cci.cvr_id = cai.cvr_id - LEFT JOIN cast_vote_record AS cvr_s - ON cai.cvr_id = cvr_s.id - LEFT JOIN cvr_contest_info AS cci_a - ON cai.acvr_id = cci_a.cvr_id - AND cci_a.contest_id = cci.contest_id - LEFT JOIN - cast_vote_record AS cvr_a - ON cai.acvr_id = cvr_a.id - LEFT JOIN - contest AS cn - ON cci.contest_id = cn.id - LEFT JOIN county AS cty - ON cn.county_id = cty.id - LEFT JOIN - contest_to_audit AS cta - ON (cci.contest_id = cta.contest_id or cn.name = (select cn1.name from contest cn1 where cn1.id=cta.contest_id)) - LEFT JOIN - comparison_audit AS cpa - ON cpa.audit_reason = cta.reason and cast (cci.cvr_id as TEXT) = ANY (string_to_array(substring(cpa.contest_cvr_ids from 2 for (char_length(cpa.contest_cvr_ids)-2)), ',')) - LEFT JOIN ( - -- Get matching raw IRV votes for upload ballots (if there are any in irv_ballot_interpretation). - SELECT raw_choices AS raw_choice_per_voting_computer, - contest_id, - imprinted_id - FROM irv_ballot_interpretation - WHERE irv_ballot_interpretation.record_type = 'UPLOADED' - ) as irv_up - ON irv_up.contest_id = cci.contest_id AND irv_up.imprinted_id = cvr_s.imprinted_id - LEFT JOIN ( - -- Get matching raw IRV votes for audit ballots (if there are any in irv_ballot_interpretation). - -- Note that reaudited ballots won't show up here - only the most recent audit record. - SELECT raw_choices AS raw_audit_board_selection, - contest_id, - imprinted_id - FROM irv_ballot_interpretation - WHERE irv_ballot_interpretation.record_type = 'AUDITOR_ENTERED' - ) as irv_audit - ON irv_audit.contest_id = cci.contest_id AND irv_audit.imprinted_id = cvr_s.imprinted_id + cvr_audit_info AS cai + LEFT JOIN + cvr_contest_info AS cci + ON cci.cvr_id = cai.cvr_id + LEFT JOIN cast_vote_record AS cvr_s + ON cai.cvr_id = cvr_s.id + LEFT JOIN cvr_contest_info AS cci_a + ON cai.acvr_id = cci_a.cvr_id + AND cci_a.contest_id = cci.contest_id + LEFT JOIN + cast_vote_record AS cvr_a + ON cai.acvr_id = cvr_a.id + LEFT JOIN + contest AS cn + ON cci.contest_id = cn.id + LEFT JOIN county AS cty + ON cn.county_id = cty.id + LEFT JOIN + contest_to_audit AS cta + ON (cci.contest_id = cta.contest_id or cn.name = (select cn1.name from contest cn1 where cn1.id=cta.contest_id)) + LEFT JOIN + comparison_audit AS cpa + ON cpa.audit_reason = cta.reason and cast (cci.cvr_id as TEXT) = ANY (string_to_array(substring(cpa.contest_cvr_ids from 2 for (char_length(cpa.contest_cvr_ids)-2)), ',')) ORDER BY county_name, contest_name ; From 3012281560be9394b3459480d78ed5055b4cb1dd Mon Sep 17 00:00:00 2001 From: vteague Date: Fri, 16 Aug 2024 10:45:25 +1000 Subject: [PATCH 17/20] Final commenting and logging for IRV reports. --- .../corla/query/ExportQueries.java | 43 +++++++++++++++---- .../freeandfair/corla/report/ReportRows.java | 4 +- 2 files changed, 37 insertions(+), 10 deletions(-) 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 aeaa2965..5af9f5fd 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,6 +6,7 @@ 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; @@ -126,7 +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); @@ -160,7 +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)); } @@ -283,32 +290,52 @@ public static long custCopyOut(final String sql, OutputStream to, CopyManager cm * diluted margin. This 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(Session s) { + 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")); - List comparisonAudits = ComparisonAuditQueries.sortedList(); + final List comparisonAudits = ComparisonAuditQueries.sortedList(); - for(ComparisonAudit ca : comparisonAudits) { + for(final ComparisonAudit ca : comparisonAudits) { if(ca instanceof IRVComparisonAudit) { 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(Contest contest : ca.contestResult().getContests()) { if (contest.description().equals(ContestType.IRV.toString())) { contest.choices().stream().map(ch -> choices.add(ch.name())); } else { - // FIXME VT: log - throw new RuntimeException(); + // 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()); } } - Optional summary = GenerateAssertionsSummaryQueries.matching(ca.getContestName()); + 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()) { - ContestResult contestResult = ca.contestResult(); - String winner = summary.get().getWinner(); + 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); } } } 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 c6a59168..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 @@ -385,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()); @@ -396,8 +397,7 @@ 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())); if(ca instanceof IRVComparisonAudit) { From 83410ad35312d010f6ade97c24c7a2cb57fcc69c Mon Sep 17 00:00:00 2001 From: vteague Date: Fri, 16 Aug 2024 11:06:16 +1000 Subject: [PATCH 18/20] A few more 'finals' and log messages. --- .../main/java/us/freeandfair/corla/query/ExportQueries.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 5af9f5fd..afe94fa4 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 @@ -301,7 +301,7 @@ private static void updateIRVContestResults(final Session s) { for(final ComparisonAudit ca : comparisonAudits) { if(ca instanceof IRVComparisonAudit) { - Set choices = new HashSet<>(); + 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(Contest contest : ca.contestResult().getContests()) { @@ -328,7 +328,7 @@ private static void updateIRVContestResults(final Session s) { 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, + // 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())); From 3069a761550196e08974365fc1b0115079270ea0 Mon Sep 17 00:00:00 2001 From: vteague Date: Fri, 16 Aug 2024 11:11:55 +1000 Subject: [PATCH 19/20] Better commenting about the refactored row-construction function. --- .../freeandfair/corla/report/StateReport.java | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) 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 d7c8e552..94c57aea 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 @@ -686,18 +686,20 @@ public Workbook generateExcelWorkbook() { * 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. - * FIXME VT: add descriptions, fix the style thing. - * @param row_number - * @param max_cell_number - * @param ccr - * @param summary_sheet - * @param bold_style - * @param integer_style - * @param bold_right_style - * @param decimal_style - * @param standard_style - * @param standard_right_style - * @return + * @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, CountyContestResult ccr, Sheet summary_sheet, CellStyle bold_style, CellStyle integer_style, CellStyle bold_right_style, CellStyle decimal_style, @@ -793,8 +795,9 @@ protected static CellStatus makeContestSummary(int row_number, int max_cell_numb 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. + /** 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) {}; /** From d6dd3f3cdfa2decd64368c69dc1262bcadfc1446 Mon Sep 17 00:00:00 2001 From: vteague Date: Fri, 16 Aug 2024 16:12:48 +1000 Subject: [PATCH 20/20] Better commenting about the refactored row-construction function. --- .../java/us/freeandfair/corla/query/ExportQueries.java | 8 ++++---- .../java/us/freeandfair/corla/report/StateReport.java | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) 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 afe94fa4..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 @@ -287,9 +287,9 @@ 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. This 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. + * 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) { @@ -304,7 +304,7 @@ private static void updateIRVContestResults(final Session s) { 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(Contest contest : ca.contestResult().getContests()) { + for(final Contest contest : ca.contestResult().getContests()) { if (contest.description().equals(ContestType.IRV.toString())) { contest.choices().stream().map(ch -> choices.add(ch.name())); } else { 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 94c57aea..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 @@ -485,7 +485,7 @@ public Workbook generateExcelWorkbook() { // very brief summary and a reference to the assertions csv. for (final CountyContestResult ccr : e.getValue().drivingContestResults()) { - CellStatus status = makeContestSummary(row_number, max_cell_number, ccr, summary_sheet, + 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); @@ -701,9 +701,9 @@ public Workbook generateExcelWorkbook() { * @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, CountyContestResult ccr, Sheet summary_sheet, - CellStyle bold_style, CellStyle integer_style, CellStyle bold_right_style, CellStyle decimal_style, - CellStyle standard_style, CellStyle standard_right_style) { + 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;