diff --git a/tally_ho/apps/tally/management/commands/create_result_form_csv_by_form_state.py b/tally_ho/apps/tally/management/commands/create_result_form_csv_by_form_state.py new file mode 100644 index 000000000..c7aafb72c --- /dev/null +++ b/tally_ho/apps/tally/management/commands/create_result_form_csv_by_form_state.py @@ -0,0 +1,112 @@ +from django.core.management.base import BaseCommand +import csv +import pathlib +from django.utils import timezone +from tally_ho.apps.tally.models.result_form import ResultForm +from tally_ho.libs.models.enums.entry_version import EntryVersion +from tally_ho.libs.models.enums.form_state import FormState +from django.utils.translation import gettext_lazy + +def generate_csv(): + # Retrieve the form state using the provided enum number + form_state = FormState.QUALITY_CONTROL + + # Filter result forms by form state + result_forms = ResultForm.objects.filter( + form_state=form_state, tally__id=1) + + # Generate CSV file path with timestamp + timestamp = timezone.now().strftime('%Y%m%d_%H%M%S') + csv_filename = f'result_forms_{form_state.name}_{timestamp}.csv' + csv_filepath = pathlib.Path(csv_filename) + + # Define the CSV column headers + headers = [ + 'barcode', + 'center', + 'station', + 'ballot', + 'race', + 'triggers', + 'user', + 'date', + 'audit', + 'sub_name', + ] + + # Write the filtered result forms to the CSV file + with open(csv_filepath, mode='w', newline='') as file: + writer = csv.writer(file) + writer.writerow(headers) + + for result_form in result_forms: + barcode = result_form.barcode + center = result_form.center.code, + station = result_form.station_number + ballot = result_form.ballot.number, + race = result_form.ballot.electrol_race.ballot_name + user = result_form.user.username + modified_date = result_form.modified_date + audit_resolution = "" + triggers = "" + sub_name = result_form.center.sub_constituency.name + + if (result_form.form_state == FormState.ARCHIVED) &\ + (result_form.qualitycontrol is not None): + user = result_form.qualitycontrol.user.username + modified_date =\ + result_form.qualitycontrol.modified_date_formatted + + if (result_form.form_state == FormState.QUALITY_CONTROL) &\ + (result_form.qualitycontrol is not None): + user = result_form.qualitycontrol.user.username + modified_date =\ + result_form.qualitycontrol.modified_date_formatted + + if (result_form.form_state == FormState.AUDIT) &\ + (result_form.has_recon is True) &\ + (result_form.audit is None): + recon_qs = result_form.reconciliationform_set.filter( + active=True, entry_version=EntryVersion.FINAL + ) + if len(recon_qs): + user = recon_qs[0].user.username + + if (result_form.form_state == FormState.AUDIT) &\ + (result_form.has_recon is True) &\ + (result_form.audit is not None): + audit_resolution =\ + result_form.audit.resolution_recommendation_name() + quarantine_checks =\ + [q.name for q in result_form.audit.quarantine_checks.all()] + if len(quarantine_checks): + triggers = " , ".join(quarantine_checks) + user = result_form.audit.user.username + modified_date = result_form.audit.modified_date_formatted + + # Write the data row + writer.writerow([ + barcode, + center[0], + station, + ballot[0], + race, + triggers, + user, + modified_date, + audit_resolution, + sub_name, + ]) + + print(f"CSV file has been created: {csv_filepath}") + + +class Command(BaseCommand): + help = gettext_lazy("create result_form csv by form_state.") + + def handle(self, *args, **kwargs): + self.create_result_form_csv_by_form_state() + + def create_result_form_csv_by_form_state(self): + # Generate CSV based on the form state enum number + generate_csv() diff --git a/tally_ho/apps/tally/management/commands/get_election_turnout_report_by_gender.py b/tally_ho/apps/tally/management/commands/get_election_turnout_report_by_gender.py new file mode 100644 index 000000000..045dccbdf --- /dev/null +++ b/tally_ho/apps/tally/management/commands/get_election_turnout_report_by_gender.py @@ -0,0 +1,151 @@ +import csv +import pathlib + +from django.core.management.base import BaseCommand +from django.utils.translation import gettext_lazy +from django.utils import timezone +from django.db.models import ( + F, IntegerField, Subquery, OuterRef, Sum, Case, When, Value as V, CharField +) +from django.db.models.functions import Coalesce + +from tally_ho.apps.tally.models.result import Result +from tally_ho.apps.tally.models.result_form import ResultForm +from tally_ho.apps.tally.models.station import Station +from tally_ho.libs.models.enums.entry_version import EntryVersion +from tally_ho.libs.models.enums.form_state import FormState + +def generate_csv(): + tally_id = 1 + station_gender_query =\ + Subquery( + Station.objects.filter( + tally__id=tally_id, + center__code=OuterRef( + 'center__code'), + station_number=OuterRef( + 'station_number')) + .values('gender')[:1], + output_field=IntegerField()) + station_registrants_query =\ + Subquery( + Station.objects.filter( + tally__id=tally_id, + center__code=OuterRef( + 'center__code'), + station_number=OuterRef( + 'station_number')) + .values('registrants')[:1], + output_field=IntegerField()) + + turnout_data = ResultForm.objects.filter( + tally__id=tally_id, + form_state=FormState.ARCHIVED, + ).annotate( + station_gender_code=station_gender_query, + station_registrants=station_registrants_query, + station_gender=Case( + When(station_gender_code=0, + then=V('Man')), + default=V('Woman'), + output_field=CharField()), + municipality_name=F('center__sub_constituency__name'), + municipality_code=F('center__sub_constituency__code'), + sub_race_type=F('ballot__electrol_race__ballot_name') + ).values( + 'municipality_name', + 'municipality_code', + 'station_gender_code', + 'station_gender', + 'sub_race_type' + ).annotate( + total_registrants=Sum('station_registrants') + ) + + # Generate CSV file path with timestamp + timestamp = timezone.now().strftime('%Y%m%d_%H%M%S') + csv_filename = f'turnout_report_by_mun_by_race_by_gender_{timestamp}.csv' + csv_filepath = pathlib.Path(csv_filename) + + # Define the CSV column headers + headers = [ + 'municipality_name', + 'municipality_code', + 'sub_race', + 'human', + 'voters', + 'registrants', + 'turnout', + ] + + with open(csv_filepath, mode='w', newline='') as file: + writer = csv.writer(file) + writer.writerow(headers) + + for data in turnout_data: + result_station_gender_query =\ + Subquery( + Station.objects.filter( + tally__id=tally_id, + center__code=OuterRef( + 'result_form__center__code'), + station_number=OuterRef( + 'result_form__station_number')) + .values('gender')[:1], + output_field=IntegerField()) + result_station_registrants_query =\ + Subquery( + Station.objects.filter( + tally__id=tally_id, + center__code=OuterRef( + 'result_form__center__code'), + station_number=OuterRef( + 'result_form__station_number')) + .values('registrants')[:1], + output_field=IntegerField()) + + voters = Result.objects.filter( + result_form__tally__id=tally_id, + result_form__center__sub_constituency__code=data.get('municipality_code'), + result_form__ballot__electrol_race__ballot_name=data.get('sub_race_type'), + result_form__form_state=FormState.ARCHIVED, + entry_version=EntryVersion.FINAL, + active=True, + ).annotate( + station_gender_code=result_station_gender_query, + station_registrants=result_station_registrants_query, + ).filter( + station_gender_code=data.get('station_gender_code') + ).aggregate( + voters=Coalesce(Sum('votes'), 0) + ).get('voters') + + municipality_name = data.get('municipality_name') + municipality_code = data.get('municipality_code') + sub_race = data.get('sub_race_type') + human = data.get('station_gender') + registrants = data.get('total_registrants') + + turnout = round(100 * voters / registrants, 2) + # Write the data row + writer.writerow([ + municipality_name, + municipality_code, + sub_race, + human, + voters, + registrants, + turnout, + ]) + + print(f"CSV files has been created: {csv_filepath}") + + +class Command(BaseCommand): + help = gettext_lazy("get election turnout report by gender.") + + def handle(self, *args, **kwargs): + self.get_election_turnout_report_by_gender() + + def get_election_turnout_report_by_gender(self): + generate_csv() diff --git a/tally_ho/apps/tally/management/commands/get_overvoted_result_forms_csv.py b/tally_ho/apps/tally/management/commands/get_overvoted_result_forms_csv.py new file mode 100644 index 000000000..9d75a6c62 --- /dev/null +++ b/tally_ho/apps/tally/management/commands/get_overvoted_result_forms_csv.py @@ -0,0 +1,108 @@ +import csv +import pathlib + +from django.core.management.base import BaseCommand +from django.utils.translation import gettext_lazy +from django.utils import timezone +from django.db.models import F, IntegerField, Subquery, OuterRef + +from tally_ho.apps.tally.models.reconciliation_form import ReconciliationForm +from tally_ho.apps.tally.models.station import Station +from tally_ho.libs.models.enums.entry_version import EntryVersion +from tally_ho.libs.models.enums.form_state import FormState + +def generate_csv(): + tally_id = 1 + station_id_query =\ + Subquery( + Station.objects.filter( + tally__id=tally_id, + center__code=OuterRef( + 'result_form__center__code'), + station_number=OuterRef( + 'result_form__station_number')) + .values('id')[:1], + output_field=IntegerField()) + station_registrants_query =\ + Subquery( + Station.objects.filter( + tally__id=tally_id, + id=OuterRef(('station_id_num'))) + .values('registrants')[:1], + output_field=IntegerField()) + recon_forms =\ + ReconciliationForm.objects.filter( + result_form__tally__id=tally_id, + result_form__form_state=FormState.ARCHIVED, + entry_version=EntryVersion.FINAL, + active=True + ).annotate( + barcode=F('result_form__barcode'), + station_id_num=station_id_query + ).values('barcode').annotate( + ballots_inside=F('number_ballots_inside_box'), + station_registrants=station_registrants_query, + station_id=F('station_id_num'), + station_number=F('result_form__station_number'), + center_code=F('result_form__center__code'), + sub_race_name=F('result_form__center__sub_constituency__name'), + sub_race_code=F('result_form__center__sub_constituency__code') + ) + forms =\ + [ + recon_form for recon_form in recon_forms\ + if recon_form.get( + 'ballots_inside') > recon_form.get('station_registrants') + ] + + # Generate CSV file path with timestamp + timestamp = timezone.now().strftime('%Y%m%d_%H%M%S') + csv_filename = f'overvote_forms_{timestamp}.csv' + csv_filepath = pathlib.Path(csv_filename) + + # Define the CSV column headers + headers = [ + 'barcode', + 'center_code', + 'station_number', + 'ballots_inside', + 'station_registrants', + 'sub_race_name', + 'sub_race_code', + ] + + with open(csv_filepath, mode='w', newline='') as file: + writer = csv.writer(file) + writer.writerow(headers) + + for form in forms: + barcode = form.get('barcode'), + center_code = form.get('center_code'), + station_number = form.get('station_number'), + ballots_inside = form.get('ballots_inside'), + station_registrants = form.get('station_registrants'), + sub_race_name = form.get('sub_race_name'), + sub_race_code = form.get('sub_race_code'), + + # Write the data row + writer.writerow([ + barcode[0], + center_code[0], + station_number[0], + ballots_inside[0], + station_registrants[0], + sub_race_name[0], + sub_race_code[0], + ]) + + print(f"CSV files has been created: {csv_filepath}") + + +class Command(BaseCommand): + help = gettext_lazy("create csv file with overvotes.") + + def handle(self, *args, **kwargs): + self.get_overvoted_result_forms_csv() + + def get_overvoted_result_forms_csv(self): + generate_csv() diff --git a/tally_ho/apps/tally/static/images/HNEC.jpg b/tally_ho/apps/tally/static/images/HNEC.jpg index 58d6e8c5c..fe8a924ca 100644 Binary files a/tally_ho/apps/tally/static/images/HNEC.jpg and b/tally_ho/apps/tally/static/images/HNEC.jpg differ diff --git a/tally_ho/apps/tally/static/images/hnec-logo.png b/tally_ho/apps/tally/static/images/hnec-logo.png index 8c1de8c17..02d4aa91b 100644 Binary files a/tally_ho/apps/tally/static/images/hnec-logo.png and b/tally_ho/apps/tally/static/images/hnec-logo.png differ diff --git a/tally_ho/apps/tally/static/js/download_results.js b/tally_ho/apps/tally/static/js/download_results.js index 7dd4339c4..9b4cd5da4 100644 --- a/tally_ho/apps/tally/static/js/download_results.js +++ b/tally_ho/apps/tally/static/js/download_results.js @@ -30,6 +30,64 @@ $(document).ready(function () { }); }); + $("#in-report").on("click", "#export-centers-by-mun-results", function () { + $("#export-centers-by-mun-results").html("Exporting..."); + $("#export-centers-by-mun-results").prop("disabled", true); + $.ajax({ + url: centersByMunResultsDownloadUrl, + data: { + data: JSON.stringify({ + tally_id: tallyId, + }), + }, + traditional: true, + dataType: 'json', + success: (data) => { + downloadResults(data, `centers_by_mun_results_${Date.now()}.json`); + $("#export-centers-by-mun-results").removeAttr("disabled"); + $("#export-centers-by-mun-results").html("All Centers By Mun Results JSON Export"); + }, + }); + }); + $("#in-report").on("click", "#export-centers-by-mun-c-votes-results", function () { + $("#export-centers-by-mun-c-votes-results").html("Exporting..."); + $("#export-centers-by-mun-c-votes-results").prop("disabled", true); + $.ajax({ + url: centersByMunCandidatesVotesResultsDownloadUrl, + data: { + data: JSON.stringify({ + tally_id: tallyId, + }), + }, + traditional: true, + dataType: 'json', + success: (data) => { + downloadResults(data, `centers_by_mun_c_votes_results_${Date.now()}.json`); + $("#export-centers-by-mun-c-votes-results").removeAttr("disabled"); + $("#export-centers-by-mun-c-votes-results").html("Centers By Mun Candidates Votes Results (JSON)"); + }, + }); + }); + $("#in-report").on("click", "#export-centers-stations-by-mun-c-votes-results", function () { + $("#export-centers-stations-by-mun-c-votes-results").html("Exporting..."); + $("#export-centers-stations-by-mun-c-votes-results").prop("disabled", true); + $.ajax({ + url: centersStationsByMunCandidatesVotesResultsDownloadUrl, + data: { + data: JSON.stringify({ + tally_id: tallyId, + }), + }, + traditional: true, + dataType: 'json', + success: (data) => { + downloadResults(data, `centers_stations_by_mun_c_votes_results_${Date.now()}.json`); + $("#export-centers-stations-by-mun-c-votes-results").removeAttr("disabled"); + $("#export-centers-stations-by-mun-c-votes-results").html("Centers/Stations By Mun Candidates Votes Results (JSON)"); + }, + }); + }); + $("#sub-cons-list-export").on("click", "#export-sub-cons", function () { $("#export-sub-cons").html("Exporting..."); $("#export-sub-cons").prop("disabled", true); diff --git a/tally_ho/apps/tally/templates/data/table.html b/tally_ho/apps/tally/templates/data/table.html index 1dacf9f57..6b53b97cf 100644 --- a/tally_ho/apps/tally/templates/data/table.html +++ b/tally_ho/apps/tally/templates/data/table.html @@ -28,6 +28,9 @@ const getCentersStationsUrl = '{{ get_centers_stations_url }}'; const getExportUrl = '{{ get_export_url }}'; const resultsDownloadUrl = '{{ results_download_url }}'; + const centersByMunResultsDownloadUrl = '{{ centers_by_mun_results_download_url }}'; + const centersByMunCandidatesVotesResultsDownloadUrl = '{{ centers_by_mun_candidate_votes_results_download_url }}'; + const centersStationsByMunCandidatesVotesResultsDownloadUrl = '{{ centers_stations_by_mun_candidates_votes_results_download_url }}'; const subConsDownloadUrl = '{{ sub_cons_list_download_url }}'; const resultFormsDownloadUrl = '{{ result_forms_download_url }}'; const centersAndStationsDownloadUrl = '{{ centers_and_stations_list_download_url }}'; diff --git a/tally_ho/apps/tally/templates/reports/form_results.html b/tally_ho/apps/tally/templates/reports/form_results.html index e2674ba4e..a9d0de2d6 100644 --- a/tally_ho/apps/tally/templates/reports/form_results.html +++ b/tally_ho/apps/tally/templates/reports/form_results.html @@ -12,7 +12,7 @@ {% endblock %} {% block javascript %} -{% include "data/table.html" with export_file_name='form_results_report' server_side=True tally_id=tally_id get_centers_stations_url=get_centers_stations_url results_download_url=results_download_url languageDE=languageDE deployedSiteUrl=deployedSiteUrl %} +{% include "data/table.html" with export_file_name='form_results_report' server_side=True tally_id=tally_id get_centers_stations_url=get_centers_stations_url results_download_url=results_download_url centers_by_mun_results_download_url=centers_by_mun_results_download_url languageDE=languageDE deployedSiteUrl=deployedSiteUrl %}