def gen_prelim_count_cells(options, counts): """ :param options: list of possible vote options :param counts: either a dict of vote counts or a dict that contains one :return: table cells with each vote count for the region, office, whatever, substituting '-' when no votes were reported by the entity for a vote option """ counts = counts.get(PRELIMINARY_VOTE_COUNTS, counts) output = [] for option in options: number = counts.get(option) formatted_number = intcomma(number) if number else '-' output.append('<td>%s</td>' % formatted_number) return ''.join(output)
def lookup(current, *args): """ Look up data in a single hash or a series of nested hashes, using keys (possibly variables) specified in args. Return the final value. The caller can add the additional arg 'intcomma' to format an integer result like the intcomma filter of humanize. Django templates don't support hash lookups with variable keys (such as when iterating over different age groups in table columns). In the following example, age and gender are template variables, and the dict data with an age key yields another dict keyed by gender. <p>Count by age and gender: {% lookup data age gender %}</p> The following variation will format the count like the intcomma filter of humanize: <p>Count by age and gender: {% lookup data age gender intcomma %}</p> If the keys were constant, the normal Django template support could be used: <p>Count by age and gender: {{ data.19.F }}</p> <p>Count by age and gender: {{ data.19.F|intcomma }}</p> """ int_comma = False for arg in args: if arg == '': # trailing empty string args passed when invocation uses ' as <var>' break if arg == 'intcomma': int_comma = True break # force 'intcomma' to be the last arg, when used current = current[arg] if int_comma: return intcomma(current) else: return current
def _read_dashboard(self, actual_stats): """ Read parts of the dashboard that contain data we're testing and fill in the provided dictionary with the stats we observe. This only supports a small subset of the stats on the dashboard. """ # Process the office-specific election day screen for office_id in self.all_office_ids: url = reverse('vr_dashboard:election-day-office-n', args=(office_id,)) rsp = self._request(url) actual_stats['by_office'][office_id]['summary'] = \ self._parse_headline(rsp.context['headline'], has_inactive=True) for row in rsp.context['office_centers_table']: center_id = row['polling_center_code'] assert center_id in actual_stats['by_center'], \ 'Center id %s is unexpected (not one of %s)' % \ (center_id, actual_stats['by_center'].keys()) if 'opened_today' in row: open_dt = row['opened_today'] actual_stats['by_center'][center_id]['ed_open'] = open_dt if 'inactive_for_election' in row: actual_stats['by_center'][center_id]['inactive'] = True self.assertEqual(row['tr_class'], 'inactive_for_election') for period in ['1', '2', '3', '4']: votes_reported_period = 'votes_reported_' + period if votes_reported_period in row: actual_stats['by_center'][center_id][votes_reported_period] = \ row[votes_reported_period] actual_stats['by_office'][office_id][votes_reported_period] = \ row[votes_reported_period] actual_stats['by_center'][center_id]['reported_period_' + period] = \ row['reported_period_' + period] # prelim vote counts url = reverse('vr_dashboard:election-day-preliminary') rsp = self._request(url) for office in rsp.context['offices']: if PRELIMINARY_VOTE_COUNTS in office: actual_stats['by_office'][office['office_id']]['prelim'] = { key: intcomma(value) for key, value in office[PRELIMINARY_VOTE_COUNTS].iteritems() } # process the sms screen url = reverse('vr_dashboard:sms') rsp = self._request(url) yesterday_str = rsp.context['sms_stats']['last_date'] for stats in rsp.context['message_stats_by_type']: msg_type = stats['translated_sms_type'] msg_yesterday_count = int(stats['last']) msg_total_count = int(stats['total']) actual_stats['message_stats'][msg_type] = { yesterday_str: msg_yesterday_count, 'total': msg_total_count } # look at summary stats from main election_day page url = reverse('vr_dashboard:election-day') rsp = self._request(url) actual_stats['summary'] = self._parse_headline(rsp.context['headline']) for row in rsp.context['offices']: actual_office_stats = actual_stats['by_office'][row['office_id']] actual_office_stats['opened'] = row['opened'] actual_office_stats['unopened'] = row['not_opened'] actual_office_stats['closed'] = row['closed'] actual_office_stats['not_reported_1'] = row['not_reported_1'] actual_office_stats['not_reported_2'] = row['not_reported_2'] actual_office_stats['not_reported_3'] = row['not_reported_3'] actual_office_stats['not_reported_4'] = row['not_reported_4'] # process the election day CSVs, testing something from each data row csv = self._request_csv(reverse('vr_dashboard:election-day')) # office data starts in 4th row, country-wide is last row for row in csv[3:-1]: office_id = int(row[0].split()[0]) opened = int(row[2]) # already grabbed from normal view, so make sure it matches self.assertEquals(actual_stats['by_office'][office_id]['opened'], opened) csv = self._request_csv(reverse('vr_dashboard:election-day-center')) # center data starts in 4th row for row in csv[3:]: center_id = int(row[1]) total_regs = '' if not row[3] else int(row[3]) # '' for copy center actual_stats['by_center'][center_id]['registrations'] = total_regs # inactive already read from election-day-office-n; make sure it matches active_flag = 'No' if 'inactive' in actual_stats['by_center'][center_id] else 'Yes' self.assertEqual(row[5], active_flag) actual_stats['by_center'][center_id]['opened_hm'] = row[6] if row[6] else None actual_stats['by_center'][center_id]['is_closed'] = row[11] # votes_reported_N already read from election-day-office-n; make sure it matches for period, where_in_row in (('1', 7), ('2', 8), ('3', 9), ('4', 10)): period_key = 'votes_reported_' + period if row[where_in_row]: self.assertEquals(actual_stats['by_center'][center_id][period_key], int(row[where_in_row])) else: self.assertNotIn(period_key, actual_stats['by_center'][center_id]) for office_id in self.all_office_ids: csv = self._request_csv(reverse('vr_dashboard:election-day-office-n', args=[office_id])) # The center open time has already been read from the HTML, so just # make sure that it is consistent in the CSV. # Note that if there's no CenterOpen, the context will have None but # the screen and CSV will have '-'. center_id = int(csv[3][1]) open_dt = csv[3][3] self.assertEqual( actual_stats['by_center'][center_id]['ed_open'] or '-', open_dt, 'Open for center %d different between HTML and CSV (%s, %s)' % ( center_id, actual_stats['by_center'][center_id]['ed_open'], open_dt ) ) for row in csv[3:]: center_id = int(row[1]) # inactive already read from election-day-office-n; make sure it matches active_flag = 'No' if 'inactive' in actual_stats['by_center'][center_id] else 'Yes' self.assertEqual(row[2], active_flag) for center in self.all_centers: center_id = center.center_id rsp = self._request(reverse('vr_dashboard:election-day-center-n', args=[center_id])) stats = rsp.context['stats'] actual_center_stats = actual_stats['by_center'][center_id] # The last opened time was already read (set to None if it didn't open); # make sure it matches the form on this page, which uses 'Not Opened' # instead of None for unopened. self.assertEqual( actual_center_stats['ed_open'] or 'Not Opened', stats['last_opened'], 'Open for center %d different between by-office and by-center pages (%s, %s)' % ( center_id, actual_center_stats['ed_open'], stats['last_opened'] ) ) actual_center_stats['last_report'] = stats['last_report'] for period in ('1', '2', '3', '4'): # votes for period is either '' or string form of number orig_key = 'reported_period_' + period new_key = 'reported_period_' + period + '_count' actual_center_stats[new_key] = \ self._extract_int_from_span(stats[orig_key]) if stats[orig_key] else 0 # consistency between what was already extracted for 'inactive' and this response? self.assertEqual( 'inactive' in actual_stats['by_center'][center_id], 'inactive_for_election' in rsp.context['center'] ) # messages from staff phone history = self._request( reverse('vr_dashboard:phone-history') + '?phone=%s' % self.staff_phone_number ) actual_stats['phone_history'][self.staff_phone_number] = { 'message_count': len(history.context['sms_messages']), }
def _create_election_day_data(self, expected_stats): """Create various types of election data for testing of the election day dashboard.""" # Pick open times that could vary by date based on time zone. rc_1_open_time = self.election_day_dt.replace(hour=1, minute=23) rc_2_open_time = self.election_day_dt.replace(hour=10, minute=23) # This center open time is before the election time really starts, # so it will be reported under the corresponding office as an # unopened center. open_time_3 = self.election.start_time - timedelta(hours=6) # configure election day activities by registration center center_activities = [] center_activities.append({ 'center': self.rc_1, 'open_time': rc_1_open_time, 'phone_number': STAFF_PHONE_NUMBER_PATTERN % 1, }) center_activities.append({ 'center': self.rc_2, 'open_time': rc_2_open_time, 'phone_number': STAFF_PHONE_NUMBER_PATTERN % 1, 'prelim_time': self.election_day_dt, 'prelim_option': 9, 'prelim_votes': 7312, # four digits to test intcomma formatting 'period_4_time': rc_2_open_time + timedelta(hours=6), 'period_4_count': 79, # period "5" is a report for period 4 sent on following day 'period_5_time': self.election_day_dt + timedelta(days=1), 'period_5_count': 82, }) center_activities.append({ 'center': self.rc_3, 'open_time': open_time_3, 'phone_number': STAFF_PHONE_NUMBER_PATTERN % 2, }) center_activities.append({ 'center': self.rc_4, # DOES NOT SEND CenterOpen or anything else }) center_activities.append({ 'center': self.copy_of_rc_1, # The copy center opened, coincidentally at the same time as the copied center. 'open_time': rc_1_open_time, 'phone_number': STAFF_PHONE_NUMBER_PATTERN % 3, # vote report for period 2 'period_2_time': self.election_day_dt, 'period_2_count': 4321, # four digits to test intcomma formatting }) center_activities.append({ 'center': self.rc_5, # DOES NOT SEND CenterOpen or anything else # This shares an office id with rc_1, and is also marked as # inactive for this particular election. }) # shortcuts into dictionaries expected_center_stats = expected_stats['by_center'] expected_office_stats = expected_stats['by_office'] expected_summary_stats = expected_stats['summary'] # Clear office-level summaries # (Some offices will be repeated, but it doesn't matter.) for activity in center_activities: office_id = activity['center'].office_id for key in ('opened', 'closed', 'not_reported_1', 'not_reported_2', 'not_reported_3', 'not_reported_4', 'unopened'): expected_office_stats[office_id][key] = 0 expected_office_stats[office_id]['summary'] = deepcopy(EMPTY_SUMMARY) # Create the messages, increment/set counters/fields to represent # expected dashboard data. for activity in center_activities: # shortcuts specific to this center expected_for_this_center = expected_center_stats[activity['center'].center_id] expected_for_this_office = expected_office_stats[activity['center'].office_id] expected_summary_for_this_office = expected_for_this_office['summary'] last_report_dt = None # track the last report from this center open_time = activity.get('open_time', None) if open_time: open_msg = CenterOpen(election=self.election, phone_number=activity['phone_number'], registration_center=activity['center'], creation_date=activity['open_time']) open_msg.full_clean() open_msg.save() last_report_dt = self._max_report_time(last_report_dt, activity['open_time']) # It does not count as an open if it happened too early if open_time and open_time >= self.election.start_time: expected_for_this_center['ed_open'] = open_time.strftime('%d/%m %H:%M') expected_for_this_center['opened_hm'] = open_time.strftime('%H:%M') expected_for_this_office['opened'] += 1 expected_summary_stats['opened'] += 1 expected_summary_for_this_office['opened'] += 1 else: expected_for_this_center['ed_open'] = None expected_for_this_center['opened_hm'] = None expected_for_this_office['unopened'] += 1 expected_summary_stats['unopened'] += 1 expected_summary_for_this_office['unopened'] += 1 for period in ('1', '2', '3', '4'): report_time, report_count = \ activity.get('period_' + period + '_time', None), \ activity.get('period_' + period + '_count', None) if report_time: r = PollingReport(election=self.election, phone_number=activity['phone_number'], registration_center=activity['center'], period_number=int(period), num_voters=report_count, creation_date=report_time) r.full_clean() r.save() last_report_dt = self._max_report_time(last_report_dt, report_time) expected_for_this_center['votes_reported_' + period] = report_count expected_for_this_center['reported_period_' + period] = 'has_reported' expected_for_this_center['reported_period_' + period + '_count'] = report_count expected_for_this_office['votes_reported_' + period] = report_count expected_summary_stats['votes_reported_' + period] += report_count expected_summary_for_this_office['votes_reported_' + period] += report_count if period == '4': # got period 4 report, so didn't close expected_for_this_center['is_closed'] = 'Yes' expected_for_this_office['closed'] += 1 else: if open_time and open_time >= self.election.start_time: # The effective time of the reports was just after period 2, so # if this is the period 1 or 2 report then it is overdue, and # if this is the period 3 or 4 report then it is not due yet. flag = 'has_not_reported' if period in ('1', '2') else 'not_due' expected_for_this_center['reported_period_' + period] = flag else: expected_for_this_center['reported_period_' + period] = 'no_data' expected_for_this_center['reported_period_' + period + '_count'] = 0 expected_for_this_office['not_reported_' + period] += 1 if period == '4': # no period 4 report, so didn't close expected_for_this_center['is_closed'] = 'No' # Very basic support for sending period 4 report on day after election # # It assumes that a period 4 report was also sent on election day, which # simplifies handling of votes_reported_4 counters and information on # closing. # # Period "5" is period 4 on the following day. period_5_time = activity.get('period_5_time', None) if period_5_time: period_5_count = activity['period_5_count'] period_4_count = activity['period_4_count'] r = PollingReport(election=self.election, phone_number=activity['phone_number'], registration_center=activity['center'], period_number=4, num_voters=period_5_count, creation_date=period_5_time) r.full_clean() r.save() last_report_dt = self._max_report_time(last_report_dt, period_5_time) # Add in delta to prior period 4 report delta = period_5_count - period_4_count expected_for_this_center['votes_reported_4'] += delta expected_for_this_center['reported_period_4_count'] += delta expected_for_this_office['votes_reported_4'] += delta expected_summary_stats['votes_reported_4'] += delta expected_summary_for_this_office['votes_reported_4'] += delta prelim_time = activity.get('prelim_time', None) if prelim_time: prelim = PreliminaryVoteCount(election=self.election, phone_number=activity['phone_number'], registration_center=activity['center'], option=activity['prelim_option'], num_votes=activity['prelim_votes'], creation_date=prelim_time) prelim.full_clean() prelim.save() last_report_dt = self._max_report_time(last_report_dt, prelim_time) expected_for_this_office['prelim'] = { str(activity['prelim_option']): intcomma(activity['prelim_votes']) } expected_for_this_center['last_report'] = \ 'Not Reported' if not last_report_dt else \ last_report_dt.strftime('%d/%m %H:%M') # rc_5 is inactive for this election # (CenterClosedForElection created when center was created) # Now that the office 'summary' has been set up, note where inactive should show up. expected_center_stats[self.rc_5.center_id]['inactive'] = True expected_office_stats[self.rc_5.office.id]['summary']['inactive'] += 1
def _read_dashboard(self, actual_stats): """ Read parts of the dashboard that contain data we're testing and fill in the provided dictionary with the stats we observe. This only supports a small subset of the stats on the dashboard. """ # Process the office-specific election day screen for office_id in self.all_office_ids: url = reverse('vr_dashboard:election-day-office-n', args=(office_id, )) rsp = self._request(url) actual_stats['by_office'][office_id]['summary'] = \ self._parse_headline(rsp.context['headline'], has_inactive=True) for row in rsp.context['office_centers_table']: center_id = row['polling_center_code'] assert center_id in actual_stats['by_center'], \ 'Center id %s is unexpected (not one of %s)' % \ (center_id, actual_stats['by_center'].keys()) if 'opened_today' in row: open_dt = row['opened_today'] actual_stats['by_center'][center_id]['ed_open'] = open_dt if 'inactive_for_election' in row: actual_stats['by_center'][center_id]['inactive'] = True self.assertEqual(row['tr_class'], 'inactive_for_election') for period in ['1', '2', '3', '4']: votes_reported_period = 'votes_reported_' + period if votes_reported_period in row: actual_stats['by_center'][center_id][votes_reported_period] = \ row[votes_reported_period] actual_stats['by_office'][office_id][votes_reported_period] = \ row[votes_reported_period] actual_stats['by_center'][center_id]['reported_period_' + period] = \ row['reported_period_' + period] # prelim vote counts url = reverse('vr_dashboard:election-day-preliminary') rsp = self._request(url) for office in rsp.context['offices']: if PRELIMINARY_VOTE_COUNTS in office: actual_stats['by_office'][office['office_id']]['prelim'] = { key: intcomma(value) for key, value in office[PRELIMINARY_VOTE_COUNTS].iteritems() } # process the sms screen url = reverse('vr_dashboard:sms') rsp = self._request(url) yesterday_str = rsp.context['sms_stats']['last_date'] for stats in rsp.context['message_stats_by_type']: msg_type = stats['translated_sms_type'] msg_yesterday_count = int(stats['last']) msg_total_count = int(stats['total']) actual_stats['message_stats'][msg_type] = { yesterday_str: msg_yesterday_count, 'total': msg_total_count } # look at summary stats from main election_day page url = reverse('vr_dashboard:election-day') rsp = self._request(url) actual_stats['summary'] = self._parse_headline(rsp.context['headline']) for row in rsp.context['offices']: actual_office_stats = actual_stats['by_office'][row['office_id']] actual_office_stats['opened'] = row['opened'] actual_office_stats['unopened'] = row['not_opened'] actual_office_stats['closed'] = row['closed'] actual_office_stats['not_reported_1'] = row['not_reported_1'] actual_office_stats['not_reported_2'] = row['not_reported_2'] actual_office_stats['not_reported_3'] = row['not_reported_3'] actual_office_stats['not_reported_4'] = row['not_reported_4'] # process the election day CSVs, testing something from each data row csv = self._request_csv(reverse('vr_dashboard:election-day')) # office data starts in 4th row, country-wide is last row for row in csv[3:-1]: office_id = int(row[0].split()[0]) opened = int(row[2]) # already grabbed from normal view, so make sure it matches self.assertEquals(actual_stats['by_office'][office_id]['opened'], opened) csv = self._request_csv(reverse('vr_dashboard:election-day-center')) # center data starts in 4th row for row in csv[3:]: center_id = int(row[1]) total_regs = '' if not row[3] else int( row[3]) # '' for copy center actual_stats['by_center'][center_id]['registrations'] = total_regs # inactive already read from election-day-office-n; make sure it matches active_flag = 'No' if 'inactive' in actual_stats['by_center'][ center_id] else 'Yes' self.assertEqual(row[5], active_flag) actual_stats['by_center'][center_id][ 'opened_hm'] = row[6] if row[6] else None actual_stats['by_center'][center_id]['is_closed'] = row[11] # votes_reported_N already read from election-day-office-n; make sure it matches for period, where_in_row in (('1', 7), ('2', 8), ('3', 9), ('4', 10)): period_key = 'votes_reported_' + period if row[where_in_row]: self.assertEquals( actual_stats['by_center'][center_id][period_key], int(row[where_in_row])) else: self.assertNotIn(period_key, actual_stats['by_center'][center_id]) for office_id in self.all_office_ids: csv = self._request_csv( reverse('vr_dashboard:election-day-office-n', args=[office_id])) # The center open time has already been read from the HTML, so just # make sure that it is consistent in the CSV. # Note that if there's no CenterOpen, the context will have None but # the screen and CSV will have '-'. center_id = int(csv[3][1]) open_dt = csv[3][3] self.assertEqual( actual_stats['by_center'][center_id]['ed_open'] or '-', open_dt, 'Open for center %d different between HTML and CSV (%s, %s)' % (center_id, actual_stats['by_center'][center_id]['ed_open'], open_dt)) for row in csv[3:]: center_id = int(row[1]) # inactive already read from election-day-office-n; make sure it matches active_flag = 'No' if 'inactive' in actual_stats['by_center'][ center_id] else 'Yes' self.assertEqual(row[2], active_flag) for center in self.all_centers: center_id = center.center_id rsp = self._request( reverse('vr_dashboard:election-day-center-n', args=[center_id])) stats = rsp.context['stats'] actual_center_stats = actual_stats['by_center'][center_id] # The last opened time was already read (set to None if it didn't open); # make sure it matches the form on this page, which uses 'Not Opened' # instead of None for unopened. self.assertEqual( actual_center_stats['ed_open'] or 'Not Opened', stats['last_opened'], 'Open for center %d different between by-office and by-center pages (%s, %s)' % (center_id, actual_center_stats['ed_open'], stats['last_opened'])) actual_center_stats['last_report'] = stats['last_report'] for period in ('1', '2', '3', '4'): # votes for period is either '' or string form of number orig_key = 'reported_period_' + period new_key = 'reported_period_' + period + '_count' actual_center_stats[new_key] = \ self._extract_int_from_span(stats[orig_key]) if stats[orig_key] else 0 # consistency between what was already extracted for 'inactive' and this response? self.assertEqual( 'inactive' in actual_stats['by_center'][center_id], 'inactive_for_election' in rsp.context['center']) # messages from staff phone history = self._request( reverse('vr_dashboard:phone-history') + '?phone=%s' % self.staff_phone_number) actual_stats['phone_history'][self.staff_phone_number] = { 'message_count': len(history.context['sms_messages']), }
def _create_election_day_data(self, expected_stats): """Create various types of election data for testing of the election day dashboard.""" # Pick open times that could vary by date based on time zone. rc_1_open_time = self.election_day_dt.replace(hour=1, minute=23) rc_2_open_time = self.election_day_dt.replace(hour=10, minute=23) # This center open time is before the election time really starts, # so it will be reported under the corresponding office as an # unopened center. open_time_3 = self.election.start_time - timedelta(hours=6) # configure election day activities by registration center center_activities = [] center_activities.append({ 'center': self.rc_1, 'open_time': rc_1_open_time, 'phone_number': STAFF_PHONE_NUMBER_PATTERN % 1, }) center_activities.append({ 'center': self.rc_2, 'open_time': rc_2_open_time, 'phone_number': STAFF_PHONE_NUMBER_PATTERN % 1, 'prelim_time': self.election_day_dt, 'prelim_option': 9, 'prelim_votes': 7312, # four digits to test intcomma formatting 'period_4_time': rc_2_open_time + timedelta(hours=6), 'period_4_count': 79, # period "5" is a report for period 4 sent on following day 'period_5_time': self.election_day_dt + timedelta(days=1), 'period_5_count': 82, }) center_activities.append({ 'center': self.rc_3, 'open_time': open_time_3, 'phone_number': STAFF_PHONE_NUMBER_PATTERN % 2, }) center_activities.append({ 'center': self.rc_4, # DOES NOT SEND CenterOpen or anything else }) center_activities.append({ 'center': self.copy_of_rc_1, # The copy center opened, coincidentally at the same time as the copied center. 'open_time': rc_1_open_time, 'phone_number': STAFF_PHONE_NUMBER_PATTERN % 3, # vote report for period 2 'period_2_time': self.election_day_dt, 'period_2_count': 4321, # four digits to test intcomma formatting }) center_activities.append({ 'center': self.rc_5, # DOES NOT SEND CenterOpen or anything else # This shares an office id with rc_1, and is also marked as # inactive for this particular election. }) # shortcuts into dictionaries expected_center_stats = expected_stats['by_center'] expected_office_stats = expected_stats['by_office'] expected_summary_stats = expected_stats['summary'] # Clear office-level summaries # (Some offices will be repeated, but it doesn't matter.) for activity in center_activities: office_id = activity['center'].office_id for key in ('opened', 'closed', 'not_reported_1', 'not_reported_2', 'not_reported_3', 'not_reported_4', 'unopened'): expected_office_stats[office_id][key] = 0 expected_office_stats[office_id]['summary'] = deepcopy( EMPTY_SUMMARY) # Create the messages, increment/set counters/fields to represent # expected dashboard data. for activity in center_activities: # shortcuts specific to this center expected_for_this_center = expected_center_stats[ activity['center'].center_id] expected_for_this_office = expected_office_stats[ activity['center'].office_id] expected_summary_for_this_office = expected_for_this_office[ 'summary'] last_report_dt = None # track the last report from this center open_time = activity.get('open_time', None) if open_time: open_msg = CenterOpen(election=self.election, phone_number=activity['phone_number'], registration_center=activity['center'], creation_date=activity['open_time']) open_msg.full_clean() open_msg.save() last_report_dt = self._max_report_time(last_report_dt, activity['open_time']) # It does not count as an open if it happened too early if open_time and open_time >= self.election.start_time: expected_for_this_center['ed_open'] = open_time.strftime( '%d/%m %H:%M') expected_for_this_center['opened_hm'] = open_time.strftime( '%H:%M') expected_for_this_office['opened'] += 1 expected_summary_stats['opened'] += 1 expected_summary_for_this_office['opened'] += 1 else: expected_for_this_center['ed_open'] = None expected_for_this_center['opened_hm'] = None expected_for_this_office['unopened'] += 1 expected_summary_stats['unopened'] += 1 expected_summary_for_this_office['unopened'] += 1 for period in ('1', '2', '3', '4'): report_time, report_count = \ activity.get('period_' + period + '_time', None), \ activity.get('period_' + period + '_count', None) if report_time: r = PollingReport(election=self.election, phone_number=activity['phone_number'], registration_center=activity['center'], period_number=int(period), num_voters=report_count, creation_date=report_time) r.full_clean() r.save() last_report_dt = self._max_report_time( last_report_dt, report_time) expected_for_this_center['votes_reported_' + period] = report_count expected_for_this_center['reported_period_' + period] = 'has_reported' expected_for_this_center['reported_period_' + period + '_count'] = report_count expected_for_this_office['votes_reported_' + period] = report_count expected_summary_stats['votes_reported_' + period] += report_count expected_summary_for_this_office['votes_reported_' + period] += report_count if period == '4': # got period 4 report, so didn't close expected_for_this_center['is_closed'] = 'Yes' expected_for_this_office['closed'] += 1 else: if open_time and open_time >= self.election.start_time: # The effective time of the reports was just after period 2, so # if this is the period 1 or 2 report then it is overdue, and # if this is the period 3 or 4 report then it is not due yet. flag = 'has_not_reported' if period in ( '1', '2') else 'not_due' expected_for_this_center['reported_period_' + period] = flag else: expected_for_this_center['reported_period_' + period] = 'no_data' expected_for_this_center['reported_period_' + period + '_count'] = 0 expected_for_this_office['not_reported_' + period] += 1 if period == '4': # no period 4 report, so didn't close expected_for_this_center['is_closed'] = 'No' # Very basic support for sending period 4 report on day after election # # It assumes that a period 4 report was also sent on election day, which # simplifies handling of votes_reported_4 counters and information on # closing. # # Period "5" is period 4 on the following day. period_5_time = activity.get('period_5_time', None) if period_5_time: period_5_count = activity['period_5_count'] period_4_count = activity['period_4_count'] r = PollingReport(election=self.election, phone_number=activity['phone_number'], registration_center=activity['center'], period_number=4, num_voters=period_5_count, creation_date=period_5_time) r.full_clean() r.save() last_report_dt = self._max_report_time(last_report_dt, period_5_time) # Add in delta to prior period 4 report delta = period_5_count - period_4_count expected_for_this_center['votes_reported_4'] += delta expected_for_this_center['reported_period_4_count'] += delta expected_for_this_office['votes_reported_4'] += delta expected_summary_stats['votes_reported_4'] += delta expected_summary_for_this_office['votes_reported_4'] += delta prelim_time = activity.get('prelim_time', None) if prelim_time: prelim = PreliminaryVoteCount( election=self.election, phone_number=activity['phone_number'], registration_center=activity['center'], option=activity['prelim_option'], num_votes=activity['prelim_votes'], creation_date=prelim_time) prelim.full_clean() prelim.save() last_report_dt = self._max_report_time(last_report_dt, prelim_time) expected_for_this_office['prelim'] = { str(activity['prelim_option']): intcomma(activity['prelim_votes']) } expected_for_this_center['last_report'] = \ 'Not Reported' if not last_report_dt else \ last_report_dt.strftime('%d/%m %H:%M') # rc_5 is inactive for this election # (CenterClosedForElection created when center was created) # Now that the office 'summary' has been set up, note where inactive should show up. expected_center_stats[self.rc_5.center_id]['inactive'] = True expected_office_stats[self.rc_5.office.id]['summary']['inactive'] += 1