def build_discrepancy_reports( precinct: str, precinct_agreed_df: pd.DataFrame, precinct_disagreed_df: pd.DataFrame, precinct_marks_df: pd.DataFrame) -> dominate.document: version = utils.show_version() doc = dominate.document(title='Audit Engine version: ' + version) report_head(doc) with doc: with div(cls='container'): report_headline(version) div(a('< Back', href='#', onclick='window.history.back()')) h5(precinct) for contest in precinct_marks_df['contest'].unique().tolist(): contest_disagreed_df = precinct_disagreed_df.loc[ precinct_disagreed_df['contest'] == contest] h6(contest) mount_discrepancy_table(contest, contest_disagreed_df, precinct_marks_df) return doc
def get_contest_row(contest, cmpcvr_details, style_df): cmpcvr_options = cmpcvr_details.get('options') contest_row = div(h6(u(contest)), cls='my-2') contest_df = style_df.loc[style_df['contest'] == contest] options = [ o for o in contest_df['option'].tolist() if '#contest vote_for' not in o ] for option in options: option_row = div(div(option, cls='col'), cls='row') if option in cmpcvr_options: if cmpcvr_options[option].get('selected'): option_row.attributes['class'] += ' font-weight-bold' if cmpcvr_options[option].get('PMV'): option_row += div(f"PMV: {cmpcvr_options[option].get('PMV')}", cls='col') contest_row += option_row return contest_row
def get_option_details(option) -> div: """Return 'div' element with info about 'option' instance. :param option: Option instance from which 'div' tag should be build. """ coordinates = f'{option.coordinates[0]}px, {option.coordinates[1]}px' option_attributes = [ ('name from OCR', option.name), ('name from fuzzy matching', option.fuzzy_name), ('Position (left, top)', coordinates), ] title = option.fuzzy_name or option.name option_container = div(id=title, cls='ml-1 mb-2') option_container.add(h6(title, cls='ml-2 mb-0')) option_div = div(cls='col pl-4') for option_key, option_value in option_attributes: option_div.add( StyleSummary.get_details_row(option_key, str(option_value))) option_container.add(option_div) return option_container
def visit_Text(self, node): if not self._in_dropdown: return tags.span(node.text, _class='navbar-text') return tags.h6(node.text, _class='dropdown-header')
def create_html_string(COUNTERS, BALLOTLISTS, DISAGREED_INFO_DICT): """Creates a HTML string for generating the summary file. Accesses the following: COUNTERS['ballots_processed'] COUNTERS['styles_detected'] COUNTERS['matched_ballots'] COUNTERS['non_matched_ballots'] COUNTERS['blank_ballots'] list of ballot OVERVOTED_BALLOTS list of ballot DISAGREED_BALLOTS accesses ballot pdf files per precinct and ballot_id DISAGREE_INFO_DICT is keyed by ballot_id which provides dict of contests providing error information f"{config_dict['RESOURCES_PATH']}{config_dict['DISAGREEMENTS_PATHFRAG']}{ballot.ballotdict['precinct']}/{ballot.ballotdict['ballot_id']}.pdf") list STYLES style.style_num style.number style.build_from_count files style_summary = glob.glob(f"{config_dict['RESOURCES_PATH']}{config_dict['STYLES_PATHFRAG']}{style.code}.html")[0] list VOTES_RESULTS (results for each contest) result_contest['contest_name'] result_contest['selections'] result_contest['vote_for'] result_contest['question'] result_contest['total_ballots'] result_contest['total_votes'] result_contest['undervote'] result_contest['overvote'] """ script_abs_path = os.path.abspath('assets/copy_to_clipboard.js') version = utils.show_version() doc = dominate.document(title='Audit Engine version: ' + version) with doc.head: link( rel='stylesheet', href= 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css', integrity= "sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T", crossorigin="anonymous", ) script(type='text/javascript', src=script_abs_path) with doc: with div(cls='container'): with div(cls='jumbotron'): h1('Audit Engine: {version} - vote records summary'.format( version=version)) build_time = datetime.datetime.now(datetime.timezone.utc) p(f'Summary built at: {build_time.strftime("%Y-%m-%d %H:%M:%S")}', cls='lead') with table(cls='table table-striped'): with tbody(): with tr(): th('Number of ballots processed') td(COUNTERS['ballots_processed']) with tr(): th('Number of different ballot types') td(COUNTERS['styles_detected']) with tr(): th('Number of ballots matching the CVR results') td(COUNTERS['matched_ballots']) with tr(): th('Number of ballots not matching the CVR results') td(COUNTERS['non_matched_ballots']) with tr(): th('Number of completely blank ballots') td(COUNTERS['blank_ballots']) with tr(): th('Number of overvotes') td(COUNTERS['overvoted_ballots']) with tr(): th('Number of disagreements') td(COUNTERS['disagreed_ballots']) with div(cls='my-4'): h2('Styles') with table(cls='table table-striped'): with thead(): with tr(): th('Style code', scope="col") th('Style number', scope="col") th('Based on number of ballots', scope="col") th('Built at', scope="col") with tbody(): for style in STYLES: with tr(): utc_time = datetime.datetime.utcfromtimestamp( style.timestamp) style_summary = glob.glob( f"{config_dict['RESOURCES_PATH']}{config_dict['STYLES_PATHFRAG']}{style.code}.html" )[0] td( a(style.style_num, href=os.path.realpath(style_summary), target="_blank")) td(style.number) td(style.build_from_count) td(f'{utc_time.strftime("%Y-%m-%d %H:%M:%S")}') # Tables with contests results: with div(cls='my-4'): h2('Contests results') for result_contest in VOTES_RESULTS: contest_name = result_contest['contest_name'] selections = result_contest['selections'] vote_for = result_contest['vote_for'] question = result_contest['question'] with div(cls='my-4'): h5(f'Contest results "{contest_name}" (vote for {vote_for}):' ) if question: h6(f'Question "{question}"') with table(cls='table table-striped'): with thead(): with tr(): th('#', scope="col") th('Candidate', scope="col") th('Votes', scope="col") th('%', scope="col") with tbody(): for index, candidate_name in enumerate( sort_option_names(selections.keys())): try: total_votes = result_contest['total_votes'] percent = round( (selections[candidate_name] / total_votes) * 100, 2) except ZeroDivisionError: percent = 0.0 with tr(): th(index + 1, scope="row") td(candidate_name) td(candidate_name) td(f'{percent}%') with table(cls='table table-striped'): with tbody(): with tr(): th('Total number of ballots') td(result_contest['total_ballots']) with tr(): th('Number of votes') td(result_contest['total_votes']) with tr(): th('Number of undervotes') td(result_contest['undervote']) with tr(): th('Number of overvotes') td(result_contest['overvote']) # Table with overvotes: with div(cls='my-4'): h2('Ballots with overvotes:') with table(cls='table table-striped'): with thead(): with tr(): th('#', scope="col") th('Precinct / Contest name', scope="col") th('Ballot file / Ballot and CVR status', scope="col") th('Overvotes / Contest validation status', scope="col") with tbody(): dirpath = DB.dirpath_from_dirname('overvotes') for index, ballot_id in enumerate( BALLOTLISTS['overvoted_ballots']): filepathlist = glob.glob( f"{dirpath}**/{ballot_id}i.pdf", recursive=True) if not filepathlist: continue filepath = filepathlist[0] with tr(): th(index + 1, scope="row") td('') with td(): ballot_image_filepath = os.path.abspath( filepath) a(ballot_id, href=ballot_image_filepath, target="_blank") td('') # overvotes_contests = list( # filter( # lambda x: (x.contest_ballot_status == STATUS_DICT['overvote']) or # (x.contest_cvr_status == STATUS_DICT['overvote']), ballot.ballotdict['contests'])) # for contest in overvotes_contests: # with tr(): # td() # td(contest.contest_name) # td(f"{contest.contest_ballot_status} / {contest.contest_cvr_status}") # td(contest.contest_validation if contest.contest_validation is not None else '') # Table with blank ballots: with div(cls='my-4'): h2('Blank Ballots:') with table(cls='table table-striped'): with thead(): with tr(): th('#', scope="col") th('Precinct / Contest name', scope="col") th('Ballot file / Ballot and CVR status', scope="col") th('Overvotes / Contest validation status', scope="col") with tbody(): dirpath = DB.dirpath_from_dirname('blank_ballots') for index, ballot_id in enumerate( BALLOTLISTS['blank_ballots']): filepathlist = glob.glob(f"f{dirpath}{ballot_id}i.pdf", recursive=True) if not filepathlist: continue filepath = filepathlist[0] with tr(): th(index + 1, scope="row") td('') with td(): ballot_image_filepath = os.path.abspath( filepath) a(ballot_id, href=ballot_image_filepath, target="_blank") td('') # Table with disagreements: with div(cls='my-4'): h2('Ballots with disagreements:') with table(cls='table table-striped'): with thead(): with tr(): th('#', scope="col") th('Ballot file', scope="col") th('Disagreement Details', scope="col") with tbody(): dirpath = DB.dirpath_from_dirname('disagreements') for index, ballot_id in enumerate( BALLOTLISTS['disagreed_ballots']): filepathlist = glob.glob( f"{dirpath}**/{ballot_id}i.pdf", recursive=True) if not filepathlist: continue filepath = filepathlist[0] with tr(): th(index + 1, scope="row") with td(): ballot_image_filepath = os.path.abspath( filepath) a(ballot_id, href=ballot_image_filepath, target="_blank") td( raw(f"<pre>{DISAGREED_INFO_DICT[ballot_id]}</PRE>" )) return doc