From 9631c7acf2640ceeb72f6cf5c828f761f30c1278 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Mon, 15 Aug 2022 14:09:18 -0400 Subject: [PATCH 1/4] Implement edf read & logging with dataclass --- src/electos/ballotmaker/election_data.py | 42 ++++++++++++++++++++++++ tests/test_election_data.py | 29 ++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 src/electos/ballotmaker/election_data.py create mode 100644 tests/test_election_data.py diff --git a/src/electos/ballotmaker/election_data.py b/src/electos/ballotmaker/election_data.py new file mode 100644 index 0000000..75b7710 --- /dev/null +++ b/src/electos/ballotmaker/election_data.py @@ -0,0 +1,42 @@ +import json +import logging +from dataclasses import dataclass, field +from pathlib import Path + +from electos.ballotmaker.constants import NO_DATA, NO_ERRORS, NO_FILE +from electos.datamodels.nist.indexes.element_index import ElementIndex +from electos.datamodels.nist.models.edf import ElectionReport + +log = logging.getLogger(__name__) + + +@dataclass +class ElectionData: + edf: Path + edf_error: int = field(init=False) + election_report: ElectionReport = field(init=False) + ballot_styles: ElementIndex = field(init=False) + ballot_count: int = field(init=False) + + def __post_init__(self): + # let's assume there are no errors + self.edf_error = NO_ERRORS + # haven't found any ballots yet + self.ballot_count = 0 + + if self.edf is None: + log.debug("No EDF file provided.") + self.edf_error = NO_FILE + return + if not self.edf.is_file(): + log.debug(f"EDF {self.edf} is not a file") + self.edf_error = NO_FILE + return + + """Opens the specified EDF file, counts the number of BallotStyles""" + edf_data = json.loads(self.edf.read_text()) + self.election_report = ElectionReport(**edf_data) + index = ElementIndex(self.election_report, "ElectionResults") + self.ballot_styles = index.by_type("ElectionResults.BallotStyle") + self.ballot_count = sum(1 for _ in self.ballot_styles) + log.info(f"Found {self.ballot_count} ballot styles in {self.edf}") diff --git a/tests/test_election_data.py b/tests/test_election_data.py new file mode 100644 index 0000000..1ba2fc5 --- /dev/null +++ b/tests/test_election_data.py @@ -0,0 +1,29 @@ +from pathlib import Path + +from electos.ballotmaker.constants import NO_DATA, NO_ERRORS, NO_FILE +from electos.ballotmaker.election_data import ElectionData + +imaginary_file = Path("imaginary_file.json") +test_dir = Path(__file__).parent.resolve() +# this test file must be in the same dir as this test script +test_file = Path("june_test_case.json") +full_test_path = Path(test_dir, test_file) + + +def test_no_file(): + election_no_file = ElectionData(None) + assert election_no_file.edf_error == NO_FILE + + +def test_file_missing(): + election_missing_edf = ElectionData(imaginary_file) + assert election_missing_edf.edf_error == NO_FILE + + +def test_read_edf(): + # is the test file available? + assert full_test_path.is_file() + election_data = ElectionData(full_test_path) + assert election_data.edf_error == NO_ERRORS + # the EDF needs to have at least 1 BallotStyle + assert election_data.ballot_count > 0 From 7a950dfa5a02a6fab1285ead2104e3ab35859bcb Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Mon, 15 Aug 2022 16:41:28 -0400 Subject: [PATCH 2/4] Retrieve election data --- src/electos/ballotmaker/election_data.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/electos/ballotmaker/election_data.py b/src/electos/ballotmaker/election_data.py index 75b7710..6c9ed5d 100644 --- a/src/electos/ballotmaker/election_data.py +++ b/src/electos/ballotmaker/election_data.py @@ -17,6 +17,7 @@ class ElectionData: election_report: ElectionReport = field(init=False) ballot_styles: ElementIndex = field(init=False) ballot_count: int = field(init=False) + election_name: str = field(init=False) def __post_init__(self): # let's assume there are no errors @@ -36,7 +37,26 @@ def __post_init__(self): """Opens the specified EDF file, counts the number of BallotStyles""" edf_data = json.loads(self.edf.read_text()) self.election_report = ElectionReport(**edf_data) + # get election header data + self.election_name = ( + self.election_report.election[0].name.text[0].content + ) + log.info(f"Election: {self.election_name}") index = ElementIndex(self.election_report, "ElectionResults") self.ballot_styles = index.by_type("ElectionResults.BallotStyle") - self.ballot_count = sum(1 for _ in self.ballot_styles) + # self.ballot_count = sum(1 for _ in self.ballot_styles) + for count, ballot in enumerate(self.ballot_styles, start=1): + ballot_value = ballot.external_identifier[0].value + log.info(f"Ballot: {ballot_value}") + self.ballot_count = count log.info(f"Found {self.ballot_count} ballot styles in {self.edf}") + # self.election_header = index.by_type("ElectionResults.Election") + # for element in self.election_header: + # log.info(f"Element: {element}") + # log.info(f"Header: {self.election_header}") + # loop through all types in index + # for model_type in index.types(): + # log.info(f"Model type: {model_type}") + # for element in index.by_type(model_type): + # log.info(f"Element: {element}") + # # Do something with 'elements' From 82e93c7eda9cbed97b77b642a7c595888492a8e2 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Mon, 15 Aug 2022 17:37:20 -0400 Subject: [PATCH 3/4] Clean up comments --- src/electos/ballotmaker/election_data.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/electos/ballotmaker/election_data.py b/src/electos/ballotmaker/election_data.py index 6c9ed5d..17745af 100644 --- a/src/electos/ballotmaker/election_data.py +++ b/src/electos/ballotmaker/election_data.py @@ -25,6 +25,7 @@ def __post_init__(self): # haven't found any ballots yet self.ballot_count = 0 + # ensure the EDF is a valid file if self.edf is None: log.debug("No EDF file provided.") self.edf_error = NO_FILE @@ -34,7 +35,7 @@ def __post_init__(self): self.edf_error = NO_FILE return - """Opens the specified EDF file, counts the number of BallotStyles""" + # Open the specified EDF file edf_data = json.loads(self.edf.read_text()) self.election_report = ElectionReport(**edf_data) # get election header data @@ -50,13 +51,3 @@ def __post_init__(self): log.info(f"Ballot: {ballot_value}") self.ballot_count = count log.info(f"Found {self.ballot_count} ballot styles in {self.edf}") - # self.election_header = index.by_type("ElectionResults.Election") - # for element in self.election_header: - # log.info(f"Element: {element}") - # log.info(f"Header: {self.election_header}") - # loop through all types in index - # for model_type in index.types(): - # log.info(f"Model type: {model_type}") - # for element in index.by_type(model_type): - # log.info(f"Element: {element}") - # # Do something with 'elements' From 0479733efd36c8a339c788de226a64c911531a50 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Tue, 16 Aug 2022 12:18:26 -0400 Subject: [PATCH 4/4] Retrieve remaining ballot header data --- src/electos/ballotmaker/election_data.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/electos/ballotmaker/election_data.py b/src/electos/ballotmaker/election_data.py index 17745af..82054db 100644 --- a/src/electos/ballotmaker/election_data.py +++ b/src/electos/ballotmaker/election_data.py @@ -13,11 +13,15 @@ @dataclass class ElectionData: edf: Path + # properties retrieved from the EDF edf_error: int = field(init=False) election_report: ElectionReport = field(init=False) ballot_styles: ElementIndex = field(init=False) ballot_count: int = field(init=False) election_name: str = field(init=False) + start_date: str = field(init=False) + end_date: str = field(init=False) + election_type: str = field(init=False) def __post_init__(self): # let's assume there are no errors @@ -38,14 +42,26 @@ def __post_init__(self): # Open the specified EDF file edf_data = json.loads(self.edf.read_text()) self.election_report = ElectionReport(**edf_data) + # get election header data self.election_name = ( self.election_report.election[0].name.text[0].content ) log.info(f"Election: {self.election_name}") + + self.start_date = self.election_report.election[0].start_date + log.info(f"Start date: {self.start_date}") + self.end_date = self.election_report.election[0].end_date + log.info(f"End date: {self.end_date}") + self.election_type = self.election_report.election[0].type + log.info(f"{self.election_type}") + + # index the election report to retrieve lists index = ElementIndex(self.election_report, "ElectionResults") + + # how many ballots? self.ballot_styles = index.by_type("ElectionResults.BallotStyle") - # self.ballot_count = sum(1 for _ in self.ballot_styles) + # list and count ballots for count, ballot in enumerate(self.ballot_styles, start=1): ballot_value = ballot.external_identifier[0].value log.info(f"Ballot: {ballot_value}")