Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

221 name counties during assertion generation step #228

Merged
merged 4 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 22 additions & 16 deletions client/src/component/DOS/DefineAudit/GenerateAssertionsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ class GenerateAssertionsPage extends React.Component<GenerateAssertionsPageProps

interface CombinedData {
contestName: string;
county: string;
succeeded: boolean | undefined;
retry: boolean | undefined;
winner: string;
Expand All @@ -159,6 +160,7 @@ class GenerateAssertionsPage extends React.Component<GenerateAssertionsPageProps
return (
<tr>
<td>{combinedData.contestName}</td>
<td>{combinedData.county}</td>
<td style={{color: combinedData.succeeded ? 'green' : 'red'}}>
{combinedData.succeeded === undefined ? '' : successString}
</td>
Expand All @@ -172,22 +174,24 @@ class GenerateAssertionsPage extends React.Component<GenerateAssertionsPageProps
};

// Make a CombinedData structure out of a GenerateAssertionsSummary by filling in blank status.
const fillBlankStatus = (s: DOS.GenerateAssertionsSummary): CombinedData => {
const fillBlankStatus = (s: DOS.GenerateAssertionsSummaryWithCounty): CombinedData => {
return {
contestName: s.contestName,
error: s.error,
message: s.message,
contestName: s.summary.contestName,
county: s.countyName,
error: s.summary.error,
message: s.summary.message,
retry: undefined,
succeeded: undefined,
warning: s.warning,
winner: s.winner,
warning: s.summary.warning,
winner: s.summary.winner,
};
};

// Make a CombinedData structure out of an AssertionsStatus by filling in blank summary data.
const fillBlankSummary = (s: DOS.AssertionStatus): CombinedData => {
return {
contestName: s.contestName,
county: '',
error: '',
message: '',
retry: s.retry,
Expand All @@ -198,15 +202,16 @@ class GenerateAssertionsPage extends React.Component<GenerateAssertionsPageProps
};

// Make a CombinedData structure out of an AssertionsStatus and a GenerateAssertionsSummary.
const combineSummaryAndStatus = (s: DOS.AssertionStatus, t: DOS.GenerateAssertionsSummary): CombinedData => {
const combineSummaryAndStatus = (s: DOS.AssertionStatus, t: DOS.GenerateAssertionsSummaryWithCounty): CombinedData => {
return {
contestName: s.contestName,
error: t.error,
message: t.message,
county: t.countyName,
error: t.summary.error,
message: t.summary.message,
retry: s.retry,
succeeded: s.succeeded,
warning: t.warning,
winner: t.winner,
warning: t.summary.warning,
winner: t.summary.winner,
};
};

Expand All @@ -215,8 +220,8 @@ class GenerateAssertionsPage extends React.Component<GenerateAssertionsPageProps
// Various kinds of absences are possible, because there may be empty summaries at the start;
// conversely, in later phases we may rerun generation (and hence get status) for only a few contests.
const joinRows = (statuses: DOS.AssertionGenerationStatuses
| undefined, summaries: DOS.GenerateAssertionsSummary[]) => {
summaries.sort((a, b) => a.contestName < b.contestName ? -1 : 1);
| undefined, summaries: DOS.GenerateAssertionsSummaryWithCounty[]) => {
summaries.sort((a, b) => a.summary.contestName < b.summary.contestName ? -1 : 1);
const rows: CombinedData[] = [];
let i = 0;
let j = 0;
Expand All @@ -232,14 +237,14 @@ class GenerateAssertionsPage extends React.Component<GenerateAssertionsPageProps
// filling in blank data otherwise.
statuses.sort((a, b) => a.contestName < b.contestName ? -1 : 1);
while (i < statuses.length && j < summaries.length) {
if (statuses[i].contestName === summaries[j].contestName) {
if (statuses[i].contestName === summaries[j].summary.contestName) {
// Matching contest names. Join the rows and move indices along both lists.
rows.push(combineSummaryAndStatus(statuses[i++], summaries[j++]));
} else if (statuses[i].contestName < summaries[j].contestName) {
} else if (statuses[i].contestName < summaries[j].summary.contestName) {
// We have a status with no matching summary. Fill in the summary with blanks.
// increment status index only.
rows.push(fillBlankSummary(statuses[i++]));
} else if (statuses[i].contestName < summaries[j].contestName) {
} else if (statuses[i].contestName < summaries[j].summary.contestName) {
// We have a summary with no matching status. Fill in status 'undefined'.
// Increment summary index only.
rows.push(fillBlankStatus(summaries[j++]));
Expand Down Expand Up @@ -283,6 +288,7 @@ class GenerateAssertionsPage extends React.Component<GenerateAssertionsPageProps
<thead>
<tr>
<th>Contest</th>
<th>County</th>
<th>Assertion Generation Status</th>
<th>Advise Retry</th>
<th>Winner</th>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ function mapStateToProps(dosState: DOS.AppState) {

if (contest.description === 'IRV') {
const assertionSummary = dosState.generateAssertionsSummaries.find(
element => element.contestName === contest.name);
element => element.summary.contestName === contest.name);

// Tied IRV contests or contests with failed assertion generations are not auditable
if (assertionSummary && assertionSummary.error.length > 0) {
if (assertionSummary && assertionSummary.summary.error.length > 0) {
return false;
}
}
Expand Down
9 changes: 8 additions & 1 deletion client/src/types/dos.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ declare namespace DOS {
standardizingContests?: boolean;
generatingAssertions?: boolean;
assertionsGenerated?: boolean;
generateAssertionsSummaries: DOS.GenerateAssertionsSummary[];
generateAssertionsSummaries: DOS.GenerateAssertionsSummaryWithCounty[];
assertionGenerationStatuses?: DOS.AssertionGenerationStatuses;
type: 'DOS';
}
Expand Down Expand Up @@ -50,6 +50,7 @@ declare namespace DOS {
[type: string]: number;
}

// Exactly matches the structure of the same name in the server.
interface GenerateAssertionsSummary {
id: number;
contestName: string;
Expand All @@ -59,6 +60,12 @@ declare namespace DOS {
message: string;
}

// Exactly matches the record of the same name in the server.
interface GenerateAssertionsSummaryWithCounty {
summary: GenerateAssertionsSummary;
countyName: string;
}

interface CanonicalContests {
[countyId: string]: string[];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,7 @@

package us.freeandfair.corla.json;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.*;

import javax.persistence.PersistenceException;

Expand All @@ -33,16 +27,10 @@
import us.freeandfair.corla.asm.ASMState;
import us.freeandfair.corla.asm.ASMUtilities;
import us.freeandfair.corla.asm.DoSDashboardASM;
import us.freeandfair.corla.model.AuditInfo;
import us.freeandfair.corla.model.AuditReason;
import us.freeandfair.corla.model.AuditType;
import us.freeandfair.corla.model.ContestToAudit;
import us.freeandfair.corla.model.County;
import us.freeandfair.corla.model.ComparisonAudit;
import us.freeandfair.corla.model.CountyDashboard;
import us.freeandfair.corla.model.DoSDashboard;
import us.freeandfair.corla.model.*;
import us.freeandfair.corla.persistence.Persistence;
import us.freeandfair.corla.query.ComparisonAuditQueries;
import us.freeandfair.corla.query.ContestResultQueries;
import us.freeandfair.corla.util.SuppressFBWarnings;

/**
Expand Down Expand Up @@ -116,7 +104,13 @@ public class DoSDashboardRefreshResponse {
* The generate assertions summaries, for IRV contests. Keyed by contest name (which is repeated
* in the GenerateAssertionsSummary).
*/
private final List<GenerateAssertionsSummary> my_generate_assertions_summaries;
private final List<GenerateAssertionsSummaryWithCounty> my_generate_assertions_summaries;

/**
* Placeholder string for when a contest crosses multiple counties. Used for IRV assertion-generation
* summaries.
*/
private final static String MULTIPLE_COUNTIES = "Multiple";

/**
* Constructs a new DosDashboardRefreshResponse.
Expand All @@ -132,7 +126,7 @@ public class DoSDashboardRefreshResponse {
* @param the_audit_info The election info.
* @param the_audit_reasons The reasons for auditing each contest.
* @param the_audit_types The audit type (usually either COMPARISON or NOT_AUDITABLE)
* @param the_generate_assertions_summaries The GenerateAssertionsSummaries, for IRV contests.
* @param the_generate_assertions_summaries The GenerateAssertionsSummaries, for IRV contests, with contest names added.
*/
@SuppressWarnings("PMD.ExcessiveParameterList")
protected DoSDashboardRefreshResponse(final ASMState the_asm_state,
Expand All @@ -145,7 +139,7 @@ protected DoSDashboardRefreshResponse(final ASMState the_asm_state,
final AuditInfo the_audit_info,
final SortedMap<Long, AuditReason> the_audit_reasons,
final SortedMap<Long, AuditType> the_audit_types,
final List<GenerateAssertionsSummary> the_generate_assertions_summaries) {
final List<GenerateAssertionsSummaryWithCounty> the_generate_assertions_summaries) {
my_asm_state = the_asm_state;
my_audited_contests = the_audited_contests;
my_estimated_ballots_to_audit = the_estimated_ballots_to_audit;
Expand Down Expand Up @@ -233,17 +227,43 @@ public static DoSDashboardRefreshResponse createResponse(final DoSDashboard dash
final DoSDashboardASM asm =
ASMUtilities.asmFor(DoSDashboardASM.class, DoSDashboardASM.IDENTITY);

// Load all the Generate Assertions Summaries from the database into the generate_assertions_list.
final List<GenerateAssertionsSummary> generate_assertions_list
= Persistence.getAll(GenerateAssertionsSummary.class);


return new DoSDashboardRefreshResponse(asm.currentState(), audited_contests,
estimated_ballots_to_audit,
optimistic_ballots_to_audit, discrepancy_count,
countyStatusMap(), hand_count_contests,
dashboard.auditInfo(), audit_reasons, audit_types,
generate_assertions_list);
addCountiesToSummaries());
}


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does not have a pre-method comment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Thanks.

private static List<GenerateAssertionsSummaryWithCounty> addCountiesToSummaries() {
List<GenerateAssertionsSummaryWithCounty> generateAssertionsSummaries = new ArrayList<>();

// Load all the Generate Assertions Summaries from the database into the generate_assertions_list.
final List<GenerateAssertionsSummary> generate_assertions_list
= Persistence.getAll(GenerateAssertionsSummary.class);

// Find out which county each contest is in; fill in 'Multiple' if there is more than one.
for (GenerateAssertionsSummary summary : generate_assertions_list) {
final Optional<ContestResult> cr = ContestResultQueries.find(summary.getContestName());
String countyName = "";
if (cr.isEmpty() || cr.get().getCounties().isEmpty()) {
// This isn't supposed to happen. Keep the summary, with a blank county name, and continue but warn.
LOGGER.warn(String.format("%s %s %s.", "[addCountiesToSummaries] ", "Empty ContestResult or County Name for County ",
summary.getContestName()));
} else {
Set<County> counties = cr.get().getCounties();
if (counties.size() == 1) {
countyName = counties.stream().findFirst().get().name();
} else {
// Must be >1 because we already checked for zero.
countyName = MULTIPLE_COUNTIES;
}
}
// Add the summary in, whether we found a county or not.
generateAssertionsSummaries.add(new GenerateAssertionsSummaryWithCounty(summary, countyName));
}
return generateAssertionsSummaries;
}

/**
Expand All @@ -267,4 +287,12 @@ private static SortedMap<Long, CountyDashboardRefreshResponse> countyStatusMap()

return status_map;
}

/**
* The same as a GenerateAssertionsSummary, but with the county name attached, or "multiple" if more than one.
*/
protected record GenerateAssertionsSummaryWithCounty(
GenerateAssertionsSummary summary,
String countyName
){};
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ public static ContestResult findOrCreate(final String contestName) {
}
}

/**
* Return an Optional ContestResult, which is Present if a contest of the requested name is in
* the database.
* @param contestName the name of the contest.
* @return an Optional<ContestResult>, containing the contest requested by name if present,
* otherwise empty.
*/
public static Optional<ContestResult> find(final String contestName) {
final Session s = Persistence.currentSession();
final Query<ContestResult> q = s.createQuery("select cr from ContestResult cr " +
"where cr.contestName = :contestName", ContestResult.class);
q.setParameter("contestName", contestName);
return q.uniqueResultOptional();
}

/**
* Return the ContestResult with the contestName given or create a new
* ContestResult with the contestName.
Expand Down
11 changes: 11 additions & 0 deletions server/eclipse-project/src/test/workflows/demo1_defineAudit.http
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ Content-Type: application/json
GET http://localhost:8888/generate-assertions?timeLimitSeconds=1
Content-Type: application/x-www-form-urlencoded

> {%
client.test("Request executed successfully", function() {
client.assert(response.status === 200, "Response status is not 200");
});

client.test("Response content-type is json", function() {
var type = response.contentType.mimeType;
client.assert(type === "application/json", "Expected 'application/json' but received '" + type + "'");
});
%}

### Request contests, search for IDs by name.
GET http://localhost:8888/contest
Content-Type: text/plain
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#Ephemeral database config for Demo1
#Fri Nov 15 15:26:53 AEDT 2024
#Fri Dec 06 10:45:39 AEDT 2024
raire_url=http\://localhost\:8080
generate_assertions_mock_port=8110
hibernate.driver=org.postgresql.Driver
Expand Down
Loading