def create(center_without_office=False, num_copy_centers=DEFAULT_NUM_COPY_CENTERS, num_registrations=DEFAULT_NUM_REGISTRATIONS, num_registration_dates=DEFAULT_NUM_REGISTRATION_DATES, num_daily_reports=DEFAULT_NUM_DAILY_REPORTS, num_registration_centers=DEFAULT_NUM_REGISTRATION_CENTERS, num_subconstituencies=DEFAULT_NUM_SUBCONSTITUENCIES, use_existing_infra=False, num_inactive_centers_per_election=DEFAULT_NUM_INACTIVE_PER_ELECTION, num_no_reg_centers=DEFAULT_NUM_NO_REG_CENTERS, election_dates=()): assert settings.ENVIRONMENT not in ('production', 'testing') delete(delete_infra=not use_existing_infra) empty_report_store() # Remove any old data from Redis # Figure out ~10% of "normal" centers... fraction_of_normal_centers = \ max(1, int(0.1 * num_registration_centers)) if num_registration_centers else 0 # If numbers of some weird center types weren't specified, use a small # fraction of normal centers. if num_copy_centers == DEFAULT_NUM_COPY_CENTERS: num_copy_centers = fraction_of_normal_centers if num_no_reg_centers == DEFAULT_NUM_NO_REG_CENTERS: num_no_reg_centers = fraction_of_normal_centers carrier = BackendFactory() if election_dates: elections = [ ElectionFactory(polling_start_time=election_date.replace(hour=8), polling_end_time=election_date.replace(hour=20)) for election_date in election_dates ] else: election_date = PAST_DAY.replace(hour=8, microsecond=123456) election = ElectionFactory( polling_start_time=election_date, polling_end_time=election_date.replace(hour=20)) elections = (election, ) if not use_existing_infra: OfficeFactory() ConstituencyFactory(name_english='first') SubConstituencyFactory(name_english='Benghazi') offices = Office.objects.all() copy_centers = [] no_reg_centers = [] staff_phones = [] if use_existing_infra: # Pick centers that support registrations at random. centers = RegistrationCenter.objects.filter(reg_open=True)\ .exclude(center_type=RegistrationCenter.Types.COPY)\ .order_by('?')[:num_registration_centers] if num_copy_centers: # user wants some, but there might not be any copy_centers = RegistrationCenter.objects.\ filter(reg_open=True, center_type=RegistrationCenter.Types.COPY)\ .order_by('?')[:num_copy_centers] if num_no_reg_centers: # user wants some, but there might not be any no_reg_centers = RegistrationCenter.objects.\ filter(reg_open=False).order_by('?')[:num_no_reg_centers] # why like this? sliced queries and/or list all_kinds_of_centers = \ list(centers) + list(copy_centers) + list(no_reg_centers) else: subconstituencies = SubConstituency.objects.exclude( pk=SPLIT_CENTER_SUBCONSTITUENCY_ID) subconstituencies = subconstituencies[:num_subconstituencies] centers = [] for i in range(num_registration_centers): constituency = Constituency.objects.filter(name_english='first')[0] subconstituency = random.choice(subconstituencies) rc = RegistrationCenter(name='polling-center-%d' % i, center_id=CENTER_ID_MIN_INT_VALUE + i, constituency=constituency, subconstituency=subconstituency, office=random.choice(offices)) rc.full_clean() rc.save() centers.append(rc) for i in range(num_copy_centers): original = random.choice(centers) # XXX This doesn't handle accidentally making too many copies of the same # center, so make sure --num-centers is "big enough" w.r.t. --num-copy-centers. new_center_id = CENTER_ID_MIN_INT_VALUE + num_registration_centers + i copy = RegistrationCenter( name='Copy of %s' % original.name, center_id=new_center_id, constituency=original.constituency, subconstituency=original.subconstituency, office=original.office, center_type=RegistrationCenter.Types.COPY, copy_of=original) copy.full_clean() copy.save() copy_centers.append(copy) for i in range(num_no_reg_centers): constituency = Constituency.objects.filter(name_english='first')[0] subconstituency = random.choice(subconstituencies) center_id = CENTER_ID_MIN_INT_VALUE + num_registration_centers + num_copy_centers + i rc = RegistrationCenter(name='no-reg-polling-center-%d' % i, center_id=center_id, constituency=constituency, subconstituency=subconstituency, office=random.choice(offices), reg_open=False) rc.full_clean() rc.save() all_kinds_of_centers = centers + copy_centers + no_reg_centers if center_without_office: try: # by not specifying office and other infra, it will be "standalone" rc = RegistrationCenter(name='dummy-registration-center', center_id=UNUSED_CENTER_ID) rc.full_clean() rc.save() except ValidationError: pass # assume that it already exists for election in elections: num_daily_reports_on_election_day = int(round(0.9 * num_daily_reports)) centers_reported = set() for i in range(num_daily_reports_on_election_day): staff_phone_number = STAFF_PHONE_NUMBER_PATTERN % i from_center = random.choice(all_kinds_of_centers) ensure_staff_phone_exists( staff_phone_number, from_center, staff_phones, election.work_start_time + datetime.timedelta(minutes=5)) # split votes between two options number_of_votes = (random.randint(1, 100), random.randint(1, 100)) random_period_number = random.randint(FIRST_PERIOD_NUMBER, LAST_PERIOD_NUMBER) pr = PollingReport(election=election, phone_number=staff_phone_number, registration_center=from_center, period_number=random_period_number, num_voters=sum(number_of_votes), creation_date=election.polling_start_time) pr.full_clean() pr.save() s = SMS(from_number=staff_phone_number, to_number=POLLING_REPORT_PHONE_NUMBER, direction=INCOMING, message='my message', msg_type=SMS.POLLING_REPORT, message_code=MESSAGE_1, carrier=carrier, creation_date=election.polling_start_time) s.full_clean() s.save() if from_center in centers_reported: continue # can't send but one PreliminaryVoteCount from a center # send a corresponding vote count for option, votes_for_option in enumerate(number_of_votes, start=1): pvc = PreliminaryVoteCount( election=election, phone_number=staff_phone_number, registration_center=from_center, option=option, num_votes=votes_for_option, creation_date=election.polling_start_time) pvc.full_clean() pvc.save() s = SMS( from_number=staff_phone_number, to_number=PRELIMINARY_VOTE_COUNT_PHONE_NUMBER, # XXX no specific message type for PreliminaryVoteCount direction=INCOMING, message='my message', msg_type=SMS.POLLING_REPORT, message_code=MESSAGE_1, carrier=carrier, creation_date=election.polling_start_time) s.full_clean() s.save() centers_reported.add(from_center) # some daily reports on the day after for i in range(num_daily_reports - num_daily_reports_on_election_day): staff_phone_number = STAFF_PHONE_NUMBER_PATTERN % i rc = random.choice(all_kinds_of_centers) ensure_staff_phone_exists( staff_phone_number, rc, staff_phones, election.work_start_time + datetime.timedelta(minutes=5)) report_creation_date = election.polling_start_time + datetime.timedelta( days=1) pr = PollingReport( election=election, phone_number=staff_phone_number, registration_center=rc, period_number= LAST_PERIOD_NUMBER, # day after counts as last period num_voters=random.randint(1, 50), creation_date=report_creation_date) pr.full_clean() pr.save() s = SMS(from_number=staff_phone_number, to_number=POLLING_REPORT_PHONE_NUMBER, direction=INCOMING, message='my message', msg_type=SMS.POLLING_REPORT, message_code=MESSAGE_1, carrier=carrier, creation_date=election.polling_start_time) s.full_clean() s.save() # Tag some centers as inactive for the election. We may or may not pick some that # sent messages as being inactive. num_inactive_centers_per_election = \ min(num_inactive_centers_per_election, len(all_kinds_of_centers)) if num_inactive_centers_per_election: reordered = all_kinds_of_centers random.shuffle(reordered) for i in range(num_inactive_centers_per_election): inactive_on_election = CenterClosedForElection( registration_center=reordered[i], election=election) inactive_on_election.full_clean() inactive_on_election.save() tz = timezone(settings.TIME_ZONE) # construct a datetime that will change based on timezone discrepancies # 0-2am in Libya has a different date than the same time in UDT or EDT today_fragile = now().astimezone(tz).replace(hour=0, minute=59) # tz.normalize fixes up the date arithmetic when crossing DST boundaries creation_dates = \ [tz.normalize((today_fragile - datetime.timedelta(days=DAYS_BETWEEN_REGISTRATIONS * i)).astimezone(tz)) for i in range(num_registration_dates)] citizens = [] for i in range(num_registrations): # about 60% of registrations are for males, just as with actual data gender = MALE if random.randint(1, 100) <= 60 else FEMALE nat_id = '%d%011d' % (gender, i) creation_date = random.choice(creation_dates) modification_date = creation_date # Select voter ages from 18 years on up. voter_age = random.randint(18, 99) # If they were a certain age at any time yesterday, they are certainly that age at any time # today. yesterday = datetime.datetime.now().replace( tzinfo=tz) - datetime.timedelta(days=1) birth_date = datetime.date(yesterday.year - voter_age, yesterday.month, yesterday.day) civil_registry_id = random.randint(1, 99999999) citizen = CitizenFactory(civil_registry_id=civil_registry_id, national_id=nat_id, gender=gender, birth_date=birth_date) citizens.append(citizen) s = SMS(from_number=VOTER_PHONE_NUMBER_PATTERN % i, to_number=REGISTRATION_PHONE_NUMBER, citizen=citizen, direction=INCOMING, message='my reg message', msg_type=SMS.REGISTRATION, message_code=MESSAGE_1, carrier=carrier, creation_date=creation_date) s.full_clean() s.save() rc = random.choice(centers) confirmed = random.randint(1, 100) <= 80 # most are confirmed if confirmed: archive_time = None else: archive_time = random.choice(creation_dates) r = Registration(citizen=citizen, registration_center=rc, sms=s, archive_time=archive_time, creation_date=creation_date, modification_date=modification_date) r.full_clean() r.save() if num_registrations: # if any data being generated # generate a variety of sms messages for i in range(NUM_RANDOM_SMS_MESSAGES): sms_type = random.choice(SMS.MESSAGE_TYPES)[0] staff_phone = random.choice(staff_phones) s = SMS(from_number=staff_phone.phone_number, to_number=RANDOM_MESSAGE_PHONE_NUMBER, citizen=random.choice(citizens), direction=INCOMING, message='my long random message', msg_type=sms_type, message_code=MESSAGE_1, carrier=carrier, creation_date=random.choice(creation_dates)) s.full_clean() s.save() for election in elections: for rc in centers: i = random.randint(8888, 9999) staff_phone_number = STAFF_PHONE_NUMBER_PATTERN % i ensure_staff_phone_exists( staff_phone_number, rc, staff_phones, election.work_start_time + datetime.timedelta(minutes=5)) center_open = CenterOpen( election=election, phone_number=staff_phone_number, registration_center=rc, creation_date=election.polling_start_time.replace( hour=random.randint(0, 10), minute=23)) center_open.full_clean() center_open.save() s = SMS(from_number=staff_phone_number, to_number=ACTIVATE_PHONE_NUMBER, direction=INCOMING, message='my message', msg_type=SMS.ACTIVATE, message_code=MESSAGE_1, carrier=carrier, creation_date=election.polling_start_time) s.full_clean() s.save()
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 create(center_without_office=False, num_copy_centers=DEFAULT_NUM_COPY_CENTERS, num_registrations=DEFAULT_NUM_REGISTRATIONS, num_registration_dates=DEFAULT_NUM_REGISTRATION_DATES, num_daily_reports=DEFAULT_NUM_DAILY_REPORTS, num_registration_centers=DEFAULT_NUM_REGISTRATION_CENTERS, num_subconstituencies=DEFAULT_NUM_SUBCONSTITUENCIES, use_existing_infra=False, num_inactive_centers_per_election=DEFAULT_NUM_INACTIVE_PER_ELECTION, num_no_reg_centers=DEFAULT_NUM_NO_REG_CENTERS, election_dates=()): assert settings.ENVIRONMENT not in ('production', 'testing') delete(delete_infra=not use_existing_infra) empty_report_store() # Remove any old data from Redis # Figure out ~10% of "normal" centers... fraction_of_normal_centers = \ max(1, int(0.1 * num_registration_centers)) if num_registration_centers else 0 # If numbers of some weird center types weren't specified, use a small # fraction of normal centers. if num_copy_centers == DEFAULT_NUM_COPY_CENTERS: num_copy_centers = fraction_of_normal_centers if num_no_reg_centers == DEFAULT_NUM_NO_REG_CENTERS: num_no_reg_centers = fraction_of_normal_centers carrier = BackendFactory() if election_dates: elections = [ ElectionFactory( polling_start_time=election_date.replace(hour=8), polling_end_time=election_date.replace(hour=20) ) for election_date in election_dates ] else: election_date = PAST_DAY.replace(hour=8, microsecond=123456) election = ElectionFactory( polling_start_time=election_date, polling_end_time=election_date.replace(hour=20) ) elections = (election,) if not use_existing_infra: OfficeFactory() ConstituencyFactory(name_english='first') SubConstituencyFactory(name_english='Benghazi') offices = Office.objects.all() copy_centers = [] no_reg_centers = [] staff_phones = [] if use_existing_infra: # Pick centers that support registrations at random. centers = RegistrationCenter.objects.filter(reg_open=True)\ .exclude(center_type=RegistrationCenter.Types.COPY)\ .order_by('?')[:num_registration_centers] if num_copy_centers: # user wants some, but there might not be any copy_centers = RegistrationCenter.objects.\ filter(reg_open=True, center_type=RegistrationCenter.Types.COPY)\ .order_by('?')[:num_copy_centers] if num_no_reg_centers: # user wants some, but there might not be any no_reg_centers = RegistrationCenter.objects.\ filter(reg_open=False).order_by('?')[:num_no_reg_centers] # why like this? sliced queries and/or list all_kinds_of_centers = \ list(centers) + list(copy_centers) + list(no_reg_centers) else: subconstituencies = SubConstituency.objects.exclude(pk=SPLIT_CENTER_SUBCONSTITUENCY_ID) subconstituencies = subconstituencies[:num_subconstituencies] centers = [] for i in range(num_registration_centers): constituency = Constituency.objects.filter(name_english='first')[0] subconstituency = random.choice(subconstituencies) rc = RegistrationCenter(name='polling-center-%d' % i, center_id=CENTER_ID_MIN_INT_VALUE+i, constituency=constituency, subconstituency=subconstituency, office=random.choice(offices)) rc.full_clean() rc.save() centers.append(rc) for i in range(num_copy_centers): original = random.choice(centers) # XXX This doesn't handle accidentally making too many copies of the same # center, so make sure --num-centers is "big enough" w.r.t. --num-copy-centers. new_center_id = CENTER_ID_MIN_INT_VALUE + num_registration_centers + i copy = RegistrationCenter(name='Copy of %s' % original.name, center_id=new_center_id, constituency=original.constituency, subconstituency=original.subconstituency, office=original.office, center_type=RegistrationCenter.Types.COPY, copy_of=original) copy.full_clean() copy.save() copy_centers.append(copy) for i in range(num_no_reg_centers): constituency = Constituency.objects.filter(name_english='first')[0] subconstituency = random.choice(subconstituencies) center_id = CENTER_ID_MIN_INT_VALUE + num_registration_centers + num_copy_centers + i rc = RegistrationCenter(name='no-reg-polling-center-%d' % i, center_id=center_id, constituency=constituency, subconstituency=subconstituency, office=random.choice(offices), reg_open=False) rc.full_clean() rc.save() all_kinds_of_centers = centers + copy_centers + no_reg_centers if center_without_office: try: # by not specifying office and other infra, it will be "standalone" rc = RegistrationCenter(name='dummy-registration-center', center_id=UNUSED_CENTER_ID) rc.full_clean() rc.save() except ValidationError: pass # assume that it already exists for election in elections: num_daily_reports_on_election_day = int(round(0.9 * num_daily_reports)) centers_reported = set() for i in range(num_daily_reports_on_election_day): staff_phone_number = STAFF_PHONE_NUMBER_PATTERN % i from_center = random.choice(all_kinds_of_centers) ensure_staff_phone_exists(staff_phone_number, from_center, staff_phones, election.work_start_time + datetime.timedelta(minutes=5)) # split votes between two options number_of_votes = (random.randint(1, 100), random.randint(1, 100)) random_period_number = random.randint(FIRST_PERIOD_NUMBER, LAST_PERIOD_NUMBER) pr = PollingReport(election=election, phone_number=staff_phone_number, registration_center=from_center, period_number=random_period_number, num_voters=sum(number_of_votes), creation_date=election.polling_start_time) pr.full_clean() pr.save() s = SMS(from_number=staff_phone_number, to_number=POLLING_REPORT_PHONE_NUMBER, direction=INCOMING, message='my message', msg_type=SMS.POLLING_REPORT, message_code=MESSAGE_1, carrier=carrier, creation_date=election.polling_start_time) s.full_clean() s.save() if from_center in centers_reported: continue # can't send but one PreliminaryVoteCount from a center # send a corresponding vote count for option, votes_for_option in enumerate(number_of_votes, start=1): pvc = PreliminaryVoteCount(election=election, phone_number=staff_phone_number, registration_center=from_center, option=option, num_votes=votes_for_option, creation_date=election.polling_start_time) pvc.full_clean() pvc.save() s = SMS(from_number=staff_phone_number, to_number=PRELIMINARY_VOTE_COUNT_PHONE_NUMBER, # XXX no specific message type for PreliminaryVoteCount direction=INCOMING, message='my message', msg_type=SMS.POLLING_REPORT, message_code=MESSAGE_1, carrier=carrier, creation_date=election.polling_start_time) s.full_clean() s.save() centers_reported.add(from_center) # some daily reports on the day after for i in range(num_daily_reports - num_daily_reports_on_election_day): staff_phone_number = STAFF_PHONE_NUMBER_PATTERN % i rc = random.choice(all_kinds_of_centers) ensure_staff_phone_exists(staff_phone_number, rc, staff_phones, election.work_start_time + datetime.timedelta(minutes=5)) report_creation_date = election.polling_start_time + datetime.timedelta(days=1) pr = PollingReport(election=election, phone_number=staff_phone_number, registration_center=rc, period_number=LAST_PERIOD_NUMBER, # day after counts as last period num_voters=random.randint(1, 50), creation_date=report_creation_date) pr.full_clean() pr.save() s = SMS(from_number=staff_phone_number, to_number=POLLING_REPORT_PHONE_NUMBER, direction=INCOMING, message='my message', msg_type=SMS.POLLING_REPORT, message_code=MESSAGE_1, carrier=carrier, creation_date=election.polling_start_time) s.full_clean() s.save() # Tag some centers as inactive for the election. We may or may not pick some that # sent messages as being inactive. num_inactive_centers_per_election = \ min(num_inactive_centers_per_election, len(all_kinds_of_centers)) if num_inactive_centers_per_election: reordered = all_kinds_of_centers random.shuffle(reordered) for i in range(num_inactive_centers_per_election): inactive_on_election = CenterClosedForElection( registration_center=reordered[i], election=election ) inactive_on_election.full_clean() inactive_on_election.save() tz = timezone(settings.TIME_ZONE) # construct a datetime that will change based on timezone discrepancies # 0-2am in Libya has a different date than the same time in UDT or EDT today_fragile = now().astimezone(tz).replace(hour=0, minute=59) # tz.normalize fixes up the date arithmetic when crossing DST boundaries creation_dates = \ [tz.normalize((today_fragile - datetime.timedelta(days=DAYS_BETWEEN_REGISTRATIONS * i)).astimezone(tz)) for i in range(num_registration_dates)] citizens = [] for i in range(num_registrations): # about 60% of registrations are for males, just as with actual data gender = MALE if random.randint(1, 100) <= 60 else FEMALE nat_id = '%d%011d' % (gender, i) creation_date = random.choice(creation_dates) modification_date = creation_date # Select voter ages from 18 years on up. voter_age = random.randint(18, 99) # If they were a certain age at any time yesterday, they are certainly that age at any time # today. yesterday = datetime.datetime.now().replace(tzinfo=tz) - datetime.timedelta(days=1) birth_date = datetime.date(yesterday.year - voter_age, yesterday.month, yesterday.day) civil_registry_id = random.randint(1, 99999999) citizen = CitizenFactory(civil_registry_id=civil_registry_id, national_id=nat_id, gender=gender, birth_date=birth_date) citizens.append(citizen) s = SMS(from_number=VOTER_PHONE_NUMBER_PATTERN % i, to_number=REGISTRATION_PHONE_NUMBER, citizen=citizen, direction=INCOMING, message='my reg message', msg_type=SMS.REGISTRATION, message_code=MESSAGE_1, carrier=carrier, creation_date=creation_date) s.full_clean() s.save() rc = random.choice(centers) confirmed = random.randint(1, 100) <= 80 # most are confirmed if confirmed: archive_time = None else: archive_time = random.choice(creation_dates) r = Registration(citizen=citizen, registration_center=rc, sms=s, archive_time=archive_time, creation_date=creation_date, modification_date=modification_date) r.full_clean() r.save() if num_registrations: # if any data being generated # generate a variety of sms messages for i in range(NUM_RANDOM_SMS_MESSAGES): sms_type = random.choice(SMS.MESSAGE_TYPES)[0] staff_phone = random.choice(staff_phones) s = SMS(from_number=staff_phone.phone_number, to_number=RANDOM_MESSAGE_PHONE_NUMBER, citizen=random.choice(citizens), direction=INCOMING, message='my long random message', msg_type=sms_type, message_code=MESSAGE_1, carrier=carrier, creation_date=random.choice(creation_dates)) s.full_clean() s.save() for election in elections: for rc in centers: i = random.randint(8888, 9999) staff_phone_number = STAFF_PHONE_NUMBER_PATTERN % i ensure_staff_phone_exists(staff_phone_number, rc, staff_phones, election.work_start_time + datetime.timedelta(minutes=5)) center_open = CenterOpen( election=election, phone_number=staff_phone_number, registration_center=rc, creation_date=election.polling_start_time.replace(hour=random.randint(0, 10), minute=23)) center_open.full_clean() center_open.save() s = SMS(from_number=staff_phone_number, to_number=ACTIVATE_PHONE_NUMBER, direction=INCOMING, message='my message', msg_type=SMS.ACTIVATE, message_code=MESSAGE_1, carrier=carrier, creation_date=election.polling_start_time) s.full_clean() s.save()
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