Skip to content

Commit 24504c8

Browse files
committed
Add missing files.
1 parent a52a581 commit 24504c8

File tree

5 files changed

+251
-9
lines changed

5 files changed

+251
-9
lines changed

server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/endpoint/GenerateAssertions.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import us.freeandfair.corla.Main;
4040
import us.freeandfair.corla.model.Choice;
4141
import us.freeandfair.corla.model.ContestResult;
42+
import us.freeandfair.corla.persistence.Persistence;
4243

4344
import java.io.IOException;
4445
import java.io.UnsupportedEncodingException;
@@ -166,6 +167,9 @@ public String endpointBody(final Request the_request, final Response the_respons
166167
serverError(the_response, e.getMessage());
167168
}
168169

170+
// The only change is updating the winners in the IRV ContestResults.
171+
Persistence.flush();
172+
169173
return my_endpoint_result.get();
170174
}
171175

@@ -255,6 +259,9 @@ protected GenerateAssertionsResponseWithErrors generateAssertionsUpdateWinners(L
255259
GenerateAssertionsResponse responseFromRaire = Main.GSON.fromJson(EntityUtils.toString(raireResponse.getEntity()),
256260
GenerateAssertionsResponse.class);
257261

262+
// Update the contestRequest with a winner from raire.
263+
updateWinnerAndLosers(cr, candidates, responseFromRaire.winner);
264+
258265
LOGGER.debug(String.format("%s %s %s.", prefix,
259266
"Completed assertion generation for contest", contestName));
260267
return new GenerateAssertionsResponseWithErrors(contestName, responseFromRaire.winner, "");
@@ -267,6 +274,9 @@ protected GenerateAssertionsResponseWithErrors generateAssertionsUpdateWinners(L
267274
LOGGER.debug(String.format("%s %s %s.", prefix, "Error response " + code,
268275
"received from RAIRE for " + contestName));
269276

277+
// Update the contestRequest with a blank winner.
278+
updateNoWinnerAndAllLosers(cr, candidates);
279+
270280
LOGGER.debug(String.format("%s %s %s.", prefix,
271281
"Error response for assertion generation for contest ", contestName));
272282
return new GenerateAssertionsResponseWithErrors(cr.getContestName(), UNKNOWN_WINNER, code);
@@ -321,6 +331,29 @@ protected GenerateAssertionsResponseWithErrors generateAssertionsUpdateWinners(L
321331
}
322332
}
323333

334+
/**
335+
* Update the contestResults in the database for failed assertion generation: no winners.
336+
* Set all candidates as losers.
337+
* @param cr the contestResult to be updated.
338+
* @param candidates the candidates.
339+
*/
340+
private void updateNoWinnerAndAllLosers(ContestResult cr, List<String> candidates) {
341+
cr.setWinners(Set.of());
342+
cr.setLosers(new HashSet<>(candidates));
343+
}
344+
345+
/**
346+
* Update the contestResults in the database according to RAIRE's assessed winners. Set all
347+
* non-winners to be losers.
348+
* @param cr the contest result, i.e. aggregated (possibly cross-county) IRV contest.
349+
* @param candidates the list of candidate names.
350+
* @param winner the winner, as determined by raire.
351+
*/
352+
private void updateWinnerAndLosers(ContestResult cr, List<String> candidates, String winner) {
353+
cr.setWinners(Set.of(winner));
354+
cr.setLosers(candidates.stream().filter(c -> !c.equalsIgnoreCase(winner)).collect(Collectors.toSet()));
355+
}
356+
324357
/**
325358
* Validates the parameters of a request. For this endpoint, the query parameters are optional,
326359
* but if the contest is present it should be non-null, and if a time limit is present it should
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
Democracy Developers IRV extensions to colorado-rla.
3+
4+
@copyright 2024 Colorado Department of State
5+
6+
These IRV extensions are designed to connect to a running instance of the raire
7+
service (https://github.com/DemocracyDevelopers/raire-service), in order to
8+
generate assertions that can be audited using colorado-rla.
9+
10+
The colorado-rla IRV extensions are free software: you can redistribute it and/or modify it under the terms
11+
of the GNU Affero General Public License as published by the Free Software Foundation, either
12+
version 3 of the License, or (at your option) any later version.
13+
14+
The colorado-rla IRV extensions are distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
15+
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16+
See the GNU Affero General Public License for more details.
17+
18+
You should have received a copy of the GNU Affero General Public License along with
19+
raire-service. If not, see <https://www.gnu.org/licenses/>.
20+
*/
21+
22+
package au.org.democracydevelopers.corla.model;
23+
24+
import org.apache.log4j.LogManager;
25+
import org.apache.log4j.Logger;
26+
27+
import javax.persistence.*;
28+
29+
import static java.util.Collections.min;
30+
31+
32+
/**
33+
* RAIRE (raire-java) generates a set of assertions for a given IRV contest, but it also returns
34+
* the winner and (possibly) an informative error. These are stored in the database, which
35+
* colorado-rla needs to read in order to produce IRV reports. This is read-only table here, with
36+
* data identical to the corresponding class in raire-service.
37+
*/
38+
@Entity
39+
@Table(name = "generate_assertions_summary")
40+
public class GenerateAssertionsSummary {
41+
42+
/**
43+
* Class-wide logger.
44+
*/
45+
private static final Logger LOGGER = LogManager.getLogger(GenerateAssertionsSummary.class);
46+
47+
/**
48+
* ID.
49+
*/
50+
@Id
51+
@Column(updatable = false, nullable = false)
52+
@GeneratedValue(strategy = GenerationType.IDENTITY)
53+
private long id;
54+
55+
/**
56+
* Version. Used for optimistic locking.
57+
*/
58+
@Version
59+
@Column(name = "version", updatable = false, nullable = false)
60+
private long version;
61+
62+
/**
63+
* Name of the contest.
64+
*/
65+
@Column(name = "contest_name", unique = true, updatable = false, nullable = false)
66+
public String contestName;
67+
68+
/**
69+
* Name of the winner of the contest, as determined by raire-java.
70+
*/
71+
@Column(name = "winner", updatable = false, nullable = false)
72+
public String winner;
73+
74+
/**
75+
* An error (matching one of the RaireServiceErrors.RaireErrorCodes), if there was one. Errors
76+
* mean there are no assertions (nor winner), but some warnings
77+
* (e.g. TIME_OUT_TRIMMING_ASSERTIONS) do have assertions and a winner, and allow the audit to
78+
* continue.
79+
*/
80+
@Column(name = "error", updatable = false, nullable = false)
81+
public String error;
82+
83+
/**
84+
* A warning, if there was one, or emptystring if none. Warnings (e.g. TIME_OUT_TRIMMING_ASSERTIONS)
85+
* mean that assertion generation succeeded and the audit can continue, but re-running with longer
86+
* time allowed might be beneficial.
87+
*/
88+
@Column(name = "warning", updatable = false, nullable = false)
89+
public String warning;
90+
91+
/**
92+
* The message associated with the error, for example the names of the tied winners.
93+
*/
94+
@Column(name = "message", updatable = false, nullable = false)
95+
public String message;
96+
97+
/**
98+
* Default no-args constructor (required for persistence).
99+
*/
100+
public GenerateAssertionsSummary() {
101+
}
102+
}

server/eclipse-project/src/main/java/au/org/democracydevelopers/corla/model/IRVComparisonAudit.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ public IRVComparisonAudit() {
106106
* thrown if an unexpected error arises when retrieving assertions from the database, or in the
107107
* computation of optimistic and estimated sample sizes.
108108
*
109-
* @param contestResult The contest result (identifies the contest under audit).
109+
* @param contestResult The contest result (identifies the contest under audit). This is updated
110+
* with the winner returned
110111
* @param riskLimit The risk limit.
111112
* @param auditReason The audit reason.
112113
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
Democracy Developers IRV extensions to colorado-rla.
3+
4+
@copyright 2024 Colorado Department of State
5+
6+
These IRV extensions are designed to connect to a running instance of the raire
7+
service (https://github.com/DemocracyDevelopers/raire-service), in order to
8+
generate assertions that can be audited using colorado-rla.
9+
10+
The colorado-rla IRV extensions are free software: you can redistribute it and/or modify it under the terms
11+
of the GNU Affero General Public License as published by the Free Software Foundation, either
12+
version 3 of the License, or (at your option) any later version.
13+
14+
The colorado-rla IRV extensions are distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
15+
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16+
See the GNU Affero General Public License for more details.
17+
18+
You should have received a copy of the GNU Affero General Public License along with
19+
raire-service. If not, see <https://www.gnu.org/licenses/>.
20+
*/
21+
22+
package au.org.democracydevelopers.corla.query;
23+
24+
import au.org.democracydevelopers.corla.model.GenerateAssertionsSummary;
25+
import au.org.democracydevelopers.corla.model.assertion.Assertion;
26+
import org.apache.log4j.LogManager;
27+
import org.apache.log4j.Logger;
28+
import org.hibernate.Session;
29+
import us.freeandfair.corla.persistence.Persistence;
30+
31+
import javax.persistence.TypedQuery;
32+
import java.util.ArrayList;
33+
import java.util.List;
34+
import java.util.Optional;
35+
36+
import static au.org.democracydevelopers.corla.endpoint.GenerateAssertions.UNKNOWN_WINNER;
37+
38+
/**
39+
* Database queries relating to the retrieval of Assertion Generation Summaries from the
40+
* database. It contains a method that executes a query to retrieval all GenerateAssertionsSummaries
41+
* belonging to a specific contest, identified by name.
42+
* Also includes a shortcut function to get the winner, which inserts UNKNOWN_WINNER if either the
43+
* record is absent, or the winner is blank.
44+
* TODO Currently these queries have no uses outside testing - if they are not needed for the UI,
45+
* (e.g. giving feedback about assertion generation) they can be safely deleted.
46+
*/
47+
public class GenerateAssertionsSummaryQueries {
48+
49+
/**
50+
* Class-wide logger.
51+
*/
52+
public static final Logger LOGGER = LogManager.getLogger(GenerateAssertionsSummaryQueries.class);
53+
54+
/**
55+
* Retrieve the winner of an IRV contest matching the given contestName, or UNKNOWN_WINNER if
56+
* there is no record, or no winner in the record.
57+
* @param contestName the name of the contest.
58+
*/
59+
public static String matchingWinner(final String contestName) {
60+
Optional<GenerateAssertionsSummary> optSummary = matching(contestName);
61+
if(optSummary.isPresent() && !optSummary.get().winner.isBlank()) {
62+
return optSummary.get().winner;
63+
} else {
64+
return UNKNOWN_WINNER;
65+
}
66+
}
67+
68+
/**
69+
* Retrieve all summaries in the database belonging to the contest with the given name.
70+
* @param contestName The contest name.
71+
* @return the (optional) summary of assertions defined for the contest.
72+
* @throws RuntimeException when an unexpected error arose in assertion retrieval (not including
73+
* a NoResultException, which is handled by returning an empty optional item).
74+
*/
75+
public static Optional<GenerateAssertionsSummary> matching(final String contestName) throws RuntimeException {
76+
final String prefix = "[matching]";
77+
try {
78+
LOGGER.debug(String.format("%s Select query on generate assertions summary table, retrieving " +
79+
"summary for contest with name %s.", prefix, contestName));
80+
81+
final Session s = Persistence.currentSession();
82+
final TypedQuery<GenerateAssertionsSummary> q = s.createQuery("select ca from GenerateAssertionsSummary ca "
83+
+ " where ca.contestName = :contestName", GenerateAssertionsSummary.class);
84+
q.setParameter("contestName", contestName);
85+
86+
List<GenerateAssertionsSummary> result = q.getResultList();
87+
LOGGER.debug(String.format("%s %d summary results retrieved for contest %s.", prefix,
88+
result.size(), contestName));
89+
if(result.isEmpty()) {
90+
// No summary was present for this contest. This is expected if GenerateAssertions has not run.
91+
return Optional.empty();
92+
} else if(result.size() == 1) {
93+
// Expected unique summary, after a run of GenerateAssertions.
94+
return Optional.of(result.get(0));
95+
} else {
96+
// Duplicate summaries - not expected, since contestName should be unique in the Generate
97+
// Assertions Summary table.
98+
throw new RuntimeException("Duplicate summaries for contest " + contestName);
99+
}
100+
}
101+
catch(Exception e){
102+
final String msg = String.format("%s An error arose when attempting to retrieve summary data" +
103+
"for contest %s: %s", prefix, contestName, e.getMessage());
104+
LOGGER.error(msg);
105+
throw new RuntimeException(msg);
106+
}
107+
}
108+
109+
}

server/eclipse-project/src/main/java/us/freeandfair/corla/report/ReportRows.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040

4141
import us.freeandfair.corla.util.SuppressFBWarnings;
4242

43+
import static au.org.democracydevelopers.corla.endpoint.GenerateAssertions.UNKNOWN_WINNER;
44+
4345
/**
4446
* Contains the query-ing and processing of two report types:
4547
* activity and results
@@ -376,17 +378,12 @@ public static List<List<String>> genSumResultsReport() {
376378
row.put("Contest", ca.contestResult().getContestName());
377379
row.put("targeted", yesNo(ca.isTargeted()));
378380

379-
if(ca instanceof IRVComparisonAudit) {
380-
// If IRV, get the winner's name from the GenerateAssertionsSummary.
381-
row.put("Winner", GenerateAssertionsSummaryQueries.matchingWinner(ca.getContestName()));
381+
if (ca.contestResult().getWinners() == null || ca.contestResult().getWinners().isEmpty()) {
382+
LOGGER.info("no winner!!! " + ca);
383+
row.put("Winner", UNKNOWN_WINNER);
382384
} else {
383-
// Must be a plurality audit. ContestResult.winner is correct.
384-
if (ca.contestResult().getWinners() == null || ca.contestResult().getWinners().isEmpty()) {
385-
LOGGER.info("no winner!!! " + ca);
386-
}
387385
row.put("Winner", toString(ca.contestResult().getWinners().iterator().next()));
388386
}
389-
// All this data makes sense for both IRV and plurality.
390387
row.put("Risk Limit met?", yesNo(riskLimitMet(ca.getRiskLimit(), riskMsmnt)));
391388
row.put("Risk measurement %", sigFig(percentage(riskMsmnt), 1).toString());
392389
row.put("Audit Risk Limit %", sigFig(percentage(ca.getRiskLimit()),1).toString());

0 commit comments

Comments
 (0)