def _form_summary_string( self, summary: TrafficViolationsAggregatorResponse) -> Optional[str]: num_vehicles = len(summary.plate_lookups) vehicle_tickets = [ len(lookup.violations) for lookup in summary.plate_lookups ] total_tickets = sum(vehicle_tickets) fines_by_vehicle: List[FineData] = [ lookup.fines for lookup in summary.plate_lookups ] vehicles_with_fines: int = len([ fine_data for fine_data in fines_by_vehicle if fine_data.fined > 0 ]) aggregate_fines: FineData = FineData( **{ field: sum( getattr(lookup.fines, field) for lookup in summary.plate_lookups) for field in FineData.FINE_FIELDS }) if aggregate_fines.fined > 0: return [ f"You queried {num_vehicles} vehicles, of which " f"{vehicles_with_fines} vehicle{L10N.pluralize(vehicles_with_fines)} " f"{'has' if vehicles_with_fines == 1 else 'have collectively'} received {total_tickets} ticket{L10N.pluralize(total_tickets)} " f"with at least {'${:,.2f}'.format(aggregate_fines.fined - aggregate_fines.reduced)} " f"in fines, of which {'${:,.2f}'.format(aggregate_fines.paid)} has been paid.\n\n" ]
def test_look_up_vehicle_with_no_violations(self, mocked_perform_query): plate = 'ABC1234' plate_types = 'PAS' state = 'NY' mocked_perform_query.side_effects = [{'data': {}} for _ in range(0, 8)] plate_query = PlateQuery( created_at='Tue Dec 31 19:28:12 -0500 2019', message_id=random.randint( 1000000000000000000, 2000000000000000000), message_source='status', plate=plate, plate_types=plate_types, state=state, username='******') result = OpenDataServiceResponse( data=OpenDataServicePlateLookup( boroughs=[], camera_streak_data=None, fines=FineData(fined=0, outstanding=0, paid=0, reduced=0), num_violations=0, plate=plate, plate_types=plate_types, state=state, violations=[], years=[]), success=True) self.assertEqual(self.open_data_service.look_up_vehicle(plate_query), result)
def _form_plate_lookup_response_parts( self, borough_data: List[Tuple[str, int]], frequency: int, fine_data: FineData, plate: str, plate_types: List[str], state: str, unique_identifier: str, username: str, violations: List[Tuple[str, int]], year_data: List[Tuple[str, int]], camera_streak_data: Optional[CameraStreakData] = None, previous_lookup: Optional[PlateLookup] = None): # response_chunks holds tweet-length-sized parts of the response # to be tweeted out or appended into a single direct message. response_chunks: List[str] = [] violations_string: str = "" # Get total violations total_violations: int = sum([s['count'] for s in violations]) LOG.debug(f'total_violations: {total_violations}') # Append to initially blank string to build tweet. violations_string += L10N.LOOKUP_SUMMARY_STRING.format( L10N.VEHICLE_HASHTAG.format(state, plate), L10N.get_plate_types_string(plate_types), frequency, L10N.pluralize(int(frequency))) # If this vehicle has been queried before... if previous_lookup: previous_num_violations: int = previous_lookup.num_tickets violations_string += self._create_repeat_lookup_string( new_violations=(total_violations - previous_num_violations), plate=plate, previous_lookup=previous_lookup, state=state, username=username) response_chunks += self._handle_response_part_formation( collection=violations, continued_format_string=L10N.LOOKUP_TICKETS_STRING_CONTD.format( L10N.VEHICLE_HASHTAG.format(state, plate)), count='count', cur_string=violations_string, description='title', default_description='No Year Available', prefix_format_string=L10N.LOOKUP_TICKETS_STRING.format( total_violations), result_format_string=L10N.LOOKUP_RESULTS_DETAIL_STRING, username=username) if year_data: response_chunks += self._handle_response_part_formation( collection=year_data, continued_format_string=L10N.LOOKUP_YEAR_STRING_CONTD.format( L10N.VEHICLE_HASHTAG.format(state, plate)), count='count', description='title', default_description='No Year Available', prefix_format_string=L10N.LOOKUP_YEAR_STRING.format( L10N.VEHICLE_HASHTAG.format(state, plate)), result_format_string=L10N.LOOKUP_RESULTS_DETAIL_STRING, username=username) if borough_data: response_chunks += self._handle_response_part_formation( collection=borough_data, continued_format_string=L10N.LOOKUP_BOROUGH_STRING_CONTD. format(L10N.VEHICLE_HASHTAG.format(state, plate)), count='count', description='title', default_description='No Borough Available', prefix_format_string=L10N.LOOKUP_BOROUGH_STRING.format( L10N.VEHICLE_HASHTAG.format(state, plate)), result_format_string=L10N.LOOKUP_RESULTS_DETAIL_STRING, username=username) if fine_data and fine_data.fines_assessed(): cur_string = f"Known fines for {L10N.VEHICLE_HASHTAG.format(state, plate)}:\n\n" max_count_length = len('${:,.2f}'.format(fine_data.max_amount())) spaces_needed = (max_count_length * 2) + 1 for fine_type, amount in fine_data: currency_string = '${:,.2f}'.format(amount) count_length = len(str(currency_string)) # e.g., if spaces_needed is 5, and count_length is 2, we need # to pad to 3. left_justify_amount = spaces_needed - count_length # formulate next string part next_part = (f"{currency_string.ljust(left_justify_amount)}| " f"{fine_type.replace('_', ' ').title()}\n") # determine current string length if necessary potential_response_length = len(username + ' ' + cur_string + next_part) # If username, space, violation string so far and new part are less or # equal than 280 characters, append to existing tweet string. if (potential_response_length <= twitter_constants.MAX_TWITTER_STATUS_LENGTH): cur_string += next_part else: response_chunks.append(cur_string) cur_string = "Known fines for #{}_{}, cont'd:\n\n" cur_string += next_part # add to container response_chunks.append(cur_string) if camera_streak_data: if camera_streak_data.max_streak and camera_streak_data.max_streak >= 5: # formulate streak string streak_string = ( f"Under @bradlander's proposed legislation, " f"this vehicle could have been booted or impounded " f"due to its {camera_streak_data.max_streak} camera violations " f"(>= 5/year) from {camera_streak_data.min_streak_date}" f" to {camera_streak_data.max_streak_date}.\n") # add to container response_chunks.append(streak_string) unique_link: str = self._get_website_plate_lookup_link( unique_identifier) website_link_string = f'View more details at {unique_link}.' response_chunks.append(website_link_string) # Send it back! return response_chunks
def _calculate_aggregate_data(self, plate_query: PlateQuery, violations) -> OpenDataServicePlateLookup: # Marshal all ticket data into form. fines: FineData = FineData(fined=round( sum(v['fined'] for v in violations.values() if v.get('fined')), 2), reduced=round( sum(v['reduced'] for v in violations.values() if v.get('reduced')), 2), paid=round( sum(v['paid'] for v in violations.values() if v.get('paid')), 2), outstanding=round( sum(v['outstanding'] for v in violations.values() if v.get('outstanding')), 2)) tickets: List[Tuple[str, int]] = Counter([ v['violation'].title() for v in violations.values() if v.get('violation') is not None ]).most_common() years: List[Tuple[str, int]] = Counter([ datetime.strptime(v['issue_date'], self.TIME_FORMAT).strftime('%Y') if v.get('has_date') else 'No Year Available' for v in violations.values() ]).most_common() boroughs: List[Tuple[str, int]] = Counter([ v['borough'].title() for v in violations.values() if v.get('borough') ]).most_common() camera_streak_data: Optional[ CameraStreakData] = self._find_max_camera_violations_streak( sorted([ datetime.strptime(v['issue_date'], self.TIME_FORMAT) for v in violations.values() if v.get('violation') and v['violation'] in self.CAMERA_VIOLATIONS ])) return OpenDataServicePlateLookup( boroughs=[{ 'count': v, 'title': k.title() } for k, v in boroughs], camera_streak_data=camera_streak_data, fines=fines, num_violations=len(violations), plate=plate_query.plate, plate_types=plate_query.plate_types, state=plate_query.state, violations=[{ 'count': v, 'title': k.title() } for k, v in tickets], years=sorted([{ 'count': v, 'title': k.title() } for k, v in years], key=lambda k: k['title']))
def test_look_up_vehicle_with_violations(self, mocked_perform_query, plate, state=None, medallion_query_result=None): random_state_index = random.randint( 0, len(regexp_constants.STATE_ABBREVIATIONS) - 1) random_plate_type_index = random.randint( 0, len(regexp_constants.PLATE_TYPES) - 1) final_plate = plate if medallion_query_result is None else medallion_query_result[0][ 'dmv_license_plate_number'] plate_type = regexp_constants.PLATE_TYPES[random_plate_type_index] state = state if state else regexp_constants.STATE_ABBREVIATIONS[random_state_index] all_borough_codes = [code for borough_list in list( BOROUGH_CODES.values()) for code in borough_list] all_precincts = [precinct for sublist in list( PRECINCTS_BY_BOROUGH.values()) for precinct in sublist] summons_numbers = set() fiscal_year_databases_violations = [] for year, _ in FISCAL_YEAR_DATABASE_ENDPOINTS.items(): fy_violations = [] for _ in range(random.randint(10, 20)): date = datetime( year, random.randint(1, 12), random.randint(1, 28)) random_borough_code_index = random.randint( 0, len(all_borough_codes) - 1) random_precinct_index = random.randint( 0, len(all_precincts) - 1) random_violation_string_index = random.randint( 0, len(HUMANIZED_NAMES_FOR_FISCAL_YEAR_DATABASE_VIOLATIONS.keys())-1) summons_number = random.randint( 1000000000, 9999999999) while summons_number in summons_numbers: summons_number = random.randint( 1000000000, 9999999999) summons_numbers.add(summons_number) fy_violations.append({ 'date_first_observed': date.strftime('%Y-%m-%dT%H:%M:%S.%f'), 'feet_from_curb': '0 ft', 'from_hours_in_effect': '0800', 'house_number': '123', 'intersecting_street': '', 'issue_date': date.strftime('%Y-%m-%dT%H:%M:%S.%f'), 'issuer_code': '43', 'issuer_command': 'C', 'issuer_precinct': all_precincts[random_precinct_index], 'issuing_agency': 'TRAFFIC', 'law_section': '123 ABC', 'plate_id': plate, 'plate_type': plate_type, 'registration_state': state, 'street_code1': '123', 'street_code2': '456', 'street_code3': '789', 'street_name': 'Fake Street', 'sub_division': '23', 'summons_image': '', 'summons_number': summons_number, 'to_hours_in_effect': '2200', 'vehicle_body_type': 'SUV', 'vehicle_color': 'BLACK', 'vehicle_expiration_date': date + timedelta(days=180), 'vehicle_make': 'FORD', 'vehicle_year': '2017', 'violation_code': list(HUMANIZED_NAMES_FOR_FISCAL_YEAR_DATABASE_VIOLATIONS.keys())[random_violation_string_index], 'violation_county': all_borough_codes[random_borough_code_index], 'violation_in_front_of_or_opposite': '', 'violation_legal_code': '4-08', 'violation_location': '', 'violation_post_code': '12345', 'violation_precinct': all_precincts[random_precinct_index]}) fiscal_year_databases_violations.append(fy_violations) open_parking_and_camera_violations = [] for _ in range(random.randint(10, 20)): fine_amount = random.randint(10, 200) interest_amount = round(float(random.randint( 0, math.floor(fine_amount/2)) + random.random()), 2) penalty_amount = round(float(random.randint( 0, math.floor(fine_amount/2)) + random.random()), 2) reduction_amount = round(float(random.randint( 0, math.floor(fine_amount/2)) + random.random()), 2) payment_amount = round(float(random.randint(0, math.floor( fine_amount + interest_amount + penalty_amount - reduction_amount))), 2) amount_due = fine_amount + interest_amount + \ penalty_amount - reduction_amount - payment_amount date = datetime( random.randint(1999, 2020), random.randint(1, 12), random.randint(1, 28)) random_borough_code_index = random.randint( 0, len(all_borough_codes) - 1) random_precinct_index = random.randint( 0, len(all_precincts) - 1) random_violation_string_index = random.randint( 0, len(HUMANIZED_NAMES_FOR_OPEN_PARKING_AND_CAMERA_VIOLATIONS.keys())-1) summons_number = random.randint(1000000000, 9999999999) summons_numbers.add(summons_number) open_parking_and_camera_violations.append({ 'amount_due': amount_due, 'county': all_borough_codes[random_borough_code_index], 'fine_amount': fine_amount, 'interest_amount': interest_amount, 'issue_date': date.strftime('%m/%d/%Y'), 'issuing_agency': 'TRAFFIC', 'license_type': plate_type, 'payment_amount': payment_amount, 'penalty_amount': penalty_amount, 'plate': plate, 'precinct': all_precincts[random_precinct_index], 'reduction_amount': reduction_amount, 'state': state, 'summons': (f'http://nycserv.nyc.gov/NYCServWeb/' f'ShowImage?searchID=VDBSVmQwNVVaekZ' f'OZWxreFQxRTlQUT09&locationName=' f'_____________________'), 'summons_image_description': 'View Summons', 'summons_number': summons_number, 'violation': list( HUMANIZED_NAMES_FOR_OPEN_PARKING_AND_CAMERA_VIOLATIONS.keys())[ random_violation_string_index], 'violation_status': 'HEARING HELD-GUILTY', 'violation_time': f'0{random.randint(1,9)}:{random.randint(10,59)}P' }) side_effects = [] if medallion_query_result: side_effects.append({'data': medallion_query_result}) side_effects.append({'data': copy.deepcopy( open_parking_and_camera_violations)}) for violations_list in fiscal_year_databases_violations: side_effects.append({'data': copy.deepcopy(violations_list)}) mocked_perform_query.side_effect = side_effects open_parking_and_camera_violations_dict = {} for summons in open_parking_and_camera_violations: summons['borough'] = [boro for boro, precincts in PRECINCTS_BY_BOROUGH.items( ) if int(summons['precinct']) in precincts][0] summons['has_date'] = True summons['issue_date'] = datetime.strptime( summons['issue_date'], '%m/%d/%Y').strftime('%Y-%m-%dT%H:%M:%S.%f') summons['violation'] = HUMANIZED_NAMES_FOR_OPEN_PARKING_AND_CAMERA_VIOLATIONS[summons['violation']] open_parking_and_camera_violations_dict[summons['summons_number']] = summons fiscal_year_databases_violations_dict = {} for violation_list in fiscal_year_databases_violations: for summons in violation_list: summons['borough'] = [boro for boro, precincts in PRECINCTS_BY_BOROUGH.items( ) if int(summons['violation_precinct']) in precincts][0] summons['has_date'] = True violation_code_definition = HUMANIZED_NAMES_FOR_FISCAL_YEAR_DATABASE_VIOLATIONS.get(summons['violation_code']) humanized_description = None if violation_code_definition: if isinstance(violation_code_definition, list) and summons['issue_date']: # Some violation codes have applied to multiple violations. for possible_description in violation_code_definition: if (datetime.strptime(possible_description['start_date'], '%Y-%m-%d').date() < datetime.strptime(summons['issue_date'], '%Y-%m-%dT%H:%M:%S.%f').date()): humanized_description = possible_description['description'] else: humanized_description = violation_code_definition summons['violation'] = humanized_description fiscal_year_databases_violations_dict[summons['summons_number']] = summons merged_violations = {**open_parking_and_camera_violations_dict, **fiscal_year_databases_violations_dict} for key, value in merged_violations.items(): if key in open_parking_and_camera_violations_dict and key in fiscal_year_databases_violations_dict: merged_violations[key] = {**open_parking_and_camera_violations_dict[key], **fiscal_year_databases_violations_dict[key]} fines_dict = {} for prefix in ['fine', 'interest', 'penalty', 'reduction', 'payment']: fines_dict[prefix] = sum(v[f'{prefix}_amount'] for v in merged_violations.values( ) if v.get(f'{prefix}_amount')) fined = round(sum([fines_dict.get('fine', 0), fines_dict.get( 'interest', 0), fines_dict.get('penalty', 0)]), 2) reduced = round(fines_dict.get('reduction', 0), 2) paid = round(fines_dict.get('payment'), 2) amount_due = round(fined - reduced - paid, 2) fines: FineData = FineData( fined=fined, reduced=reduced, paid=paid, outstanding=amount_due) tickets: list[Tuple[str, int]] = Counter([v['violation'].title() for v in merged_violations.values( ) if v.get('violation')]).most_common() years: list[Tuple[str, int]] = Counter([datetime.strptime(v['issue_date'], '%Y-%m-%dT%H:%M:%S.%f').strftime('%Y') if v.get( 'has_date') else 'No Year Available' for v in merged_violations.values()]).most_common() boroughs = Counter([v['borough'].title() for v in merged_violations.values()]).most_common() camera_streak_data = { 'Failure to Stop at Red Light': None, 'Mixed': None, 'School Zone Speed Camera Violation': None, } for violation_type in camera_streak_data: if violation_type == 'Failure to Stop at Red Light': violation_times = sorted( [datetime.strptime(v['issue_date'], '%Y-%m-%dT%H:%M:%S.%f') for v in merged_violations.values( ) if v.get('violation') == 'Failure to Stop at Red Light']) elif violation_type == 'Mixed': violation_times = sorted( [datetime.strptime(v['issue_date'], '%Y-%m-%dT%H:%M:%S.%f') for v in merged_violations.values( ) if v.get('violation') and v['violation'] in ['Failure to Stop at Red Light', 'School Zone Speed Camera Violation']]) elif violation_type == 'School Zone Speed Camera Violation': violation_times = sorted( [datetime.strptime(v['issue_date'], '%Y-%m-%dT%H:%M:%S.%f') for v in merged_violations.values( ) if v.get('violation') == 'School Zone Speed Camera Violation']) if violation_times: max_streak = 0 min_streak_date = None max_streak_date = None for date in violation_times: year_later = date + \ (datetime(date.year + 1, 1, 1) - datetime(date.year, 1, 1)) year_long_tickets = [ comp_date for comp_date in violation_times if date <= comp_date < year_later] this_streak = len(year_long_tickets) if this_streak > max_streak: max_streak = this_streak min_streak_date = year_long_tickets[0] max_streak_date = year_long_tickets[-1] camera_streak_data[violation_type] = CameraStreakData( min_streak_date=min_streak_date.strftime('%B %-d, %Y'), max_streak=max_streak, max_streak_date=max_streak_date.strftime('%B %-d, %Y')) plate_query = PlateQuery( created_at='Tue Dec 31 19:28:12 -0500 2019', message_id=random.randint( 1000000000000000000, 2000000000000000000), message_source='status', plate=plate, plate_types=plate_type, state=state, username='******') result = OpenDataServiceResponse( data=OpenDataServicePlateLookup( boroughs=[{'count': v, 'title': k.title()} for k, v in boroughs], camera_streak_data=camera_streak_data, fines=fines, num_violations=len(merged_violations), plate=final_plate, plate_types=plate_query.plate_types, state=plate_query.state, violations=[{'count': v, 'title': k.title()} for k, v in tickets], years=sorted([{'count': v, 'title': k.title()} for k, v in years], key=lambda k: k['title'])), success=True) self.assertEqual(self.open_data_service.look_up_vehicle(plate_query), result)
def _form_plate_lookup_response_parts( self, borough_data: list[Tuple[str, int]], frequency: int, fine_data: FineData, lookup_source: str, plate: str, plate_types: list[str], state: str, unique_identifier: str, username: str, violations: list[Tuple[str, int]], year_data: list[Tuple[str, int]], camera_streak_data: dict[str, CameraStreakData] = None, previous_lookup: Optional[PlateLookup] = None): # response_chunks holds tweet-length-sized parts of the response # to be tweeted out or appended into a single direct message. response_chunks: list[str] = [] violations_string: str = "" # Get total violations total_violations: int = sum([s['count'] for s in violations]) LOG.debug(f'total_violations: {total_violations}') now_in_eastern_time = self.utc.localize(datetime.now()) time_prefix = now_in_eastern_time.strftime( 'As of %I:%M:%S %p on %B %-d, %Y:\n\n') # Append username to blank string to start to build tweet. violations_string += (f'@{username} {time_prefix}' if lookup_source == lookup_sources.LookupSource.STATUS.value else '') # Append summary string. violations_string += L10N.LOOKUP_SUMMARY_STRING.format( L10N.VEHICLE_HASHTAG.format(state, plate), L10N.get_plate_types_string(plate_types), frequency, L10N.pluralize(int(frequency))) # If this vehicle has been queried before... if previous_lookup: previous_num_violations: int = previous_lookup.num_tickets violations_string += self._create_repeat_lookup_string( new_violations=(total_violations - previous_num_violations), plate=plate, previous_lookup=previous_lookup, state=state, username=username) response_chunks.append(violations_string) username_prefix = ( f'@{twitter_constants.HMDNY_TWITTER_HANDLE} {time_prefix}' if lookup_source == lookup_sources.LookupSource.STATUS.value else '') response_chunks += self._handle_response_part_formation( collection=violations, continued_format_string=L10N.LOOKUP_TICKETS_STRING_CONTD.format( L10N.VEHICLE_HASHTAG.format(state, plate)), count='count', description='title', default_description='No Year Available', prefix_format_string=L10N.LOOKUP_TICKETS_STRING.format( total_violations), result_format_string=L10N.LOOKUP_RESULTS_DETAIL_STRING, username_prefix=username_prefix) if year_data: response_chunks += self._handle_response_part_formation( collection=year_data, continued_format_string=L10N.LOOKUP_YEAR_STRING_CONTD.format( L10N.VEHICLE_HASHTAG.format(state, plate)), count='count', description='title', default_description='No Year Available', prefix_format_string=L10N.LOOKUP_YEAR_STRING.format( L10N.VEHICLE_HASHTAG.format(state, plate)), result_format_string=L10N.LOOKUP_RESULTS_DETAIL_STRING, username_prefix=username_prefix) if borough_data: response_chunks += self._handle_response_part_formation( collection=borough_data, continued_format_string=L10N.LOOKUP_BOROUGH_STRING_CONTD. format(L10N.VEHICLE_HASHTAG.format(state, plate)), count='count', description='title', default_description='No Borough Available', prefix_format_string=L10N.LOOKUP_BOROUGH_STRING.format( L10N.VEHICLE_HASHTAG.format(state, plate)), result_format_string=L10N.LOOKUP_RESULTS_DETAIL_STRING, username_prefix=username_prefix) if fine_data and fine_data.fines_assessed(): cur_string = ( f'{username_prefix}' f'Known fines for {L10N.VEHICLE_HASHTAG.format(state, plate)}:\n\n' ) max_count_length = len('${:,.2f}'.format(fine_data.max_amount())) spaces_needed = (max_count_length * 2) + 1 for fine_type, amount in fine_data: currency_string = '${:,.2f}'.format(amount) count_length = len(str(currency_string)) # e.g., if spaces_needed is 5, and count_length is 2, we need # to pad to 3. left_justify_amount = spaces_needed - count_length # formulate next string part next_part = (f"{currency_string.ljust(left_justify_amount)}| " f"{fine_type.replace('_', ' ').title()}\n") # determine current string length if necessary potential_response_length = len(cur_string + next_part) # If violation string so far and new part are less or # equal than 280 characters, append to existing tweet string. if (potential_response_length <= twitter_constants.MAX_TWITTER_STATUS_LENGTH): cur_string += next_part else: response_chunks.append(cur_string) cur_string = "Known fines for #{}_{}, cont'd:\n\n" cur_string += next_part # add to container response_chunks.append(cur_string) for camera_violation_type, threshold in self.CAMERA_THRESHOLDS.items(): violation_type_data = camera_streak_data[camera_violation_type] if violation_type_data: if (camera_violation_type == 'Failure to Stop at Red Light' and violation_type_data.max_streak >= threshold): # add to container response_chunks.append( L10N. DANGEROUS_VEHICLE_ABATEMENT_ACT_REPEAT_OFFENDER_STRING. format(username_prefix, violation_type_data.max_streak, 'red light', threshold, violation_type_data.min_streak_date, violation_type_data.max_streak_date)) elif (camera_violation_type == 'School Zone Speed Camera Violation' and violation_type_data.max_streak >= threshold): # add to container response_chunks.append( L10N. DANGEROUS_VEHICLE_ABATEMENT_ACT_REPEAT_OFFENDER_STRING. format(username_prefix, violation_type_data.max_streak, 'school zone speed', threshold, violation_type_data.min_streak_date, violation_type_data.max_streak_date)) unique_link: str = self._get_website_plate_lookup_link( unique_identifier) website_link_string = f'View more details at {unique_link}.' response_chunks.append(website_link_string) # Send it back! return response_chunks
def test_print_reckless_driver_retrospective( self, mocked_tweet_detection_service_tweet_exists, mocked_traffic_violations_tweeter, mocked_plate_lookup_get_all_in, mocked_open_data_service_look_up_vehicle, can_link_tweet=True, dry_run=False, new_camera_violations_string='10 new camera violations', red_light_camera_tickets_after_previous_lookup=3, red_light_camera_tickets_before_previous_lookup=5, speed_camera_tickets_after_previous_lookup=7, speed_camera_tickets_before_previous_lookup=15, use_dvaa_thresholds: bool = False, use_only_visible_tweets: bool = False): job = RecklessDriverRetrospectiveJob() mocked_tweet_detection_service_tweet_exists.return_value = can_link_tweet plate = 'ABCDEFG' plate_types = 'COM,PAS' state = 'NY' now = datetime.now() previous_message_id = random.randint(1000000000000000000, 2000000000000000000) previous_message_source = 'status' previous_num_tickets = 123 previous_username = '******' rdaa_max_streak = (red_light_camera_tickets_before_previous_lookup + speed_camera_tickets_before_previous_lookup) rdaa_min_streak_date = datetime(2018, 11, 26, 0, 0, 0).strftime('%B %-d, %Y') rdaa_max_streak_date = datetime(2019, 11, 22, 0, 0, 0).strftime('%B %-d, %Y') dvaa_red_light_camera_max_streak = red_light_camera_tickets_before_previous_lookup dvaa_red_light_camera_min_streak_date = datetime( 2018, 9, 14, 0, 0, 0).strftime('%B %-d, %Y') dvaa_red_light_camera_max_streak_date = datetime( 2019, 12, 17, 0, 0, 0).strftime('%B %-d, %Y') dvaa_speed_camera_max_streak = speed_camera_tickets_before_previous_lookup dvaa_speed_camera_min_streak_date = datetime(2018, 11, 26, 0, 0, 0).strftime('%B %-d, %Y') dvaa_speed_camera_max_streak_date = datetime(2019, 11, 22, 0, 0, 0).strftime('%B %-d, %Y') plate_lookup_to_return = PlateLookup( bus_lane_camera_violations=2, created_at=datetime(2020, 1, 3, 14, 37, 12), message_id=previous_message_id, message_source=previous_message_source, num_tickets=previous_num_tickets, plate=plate, plate_types=plate_types, red_light_camera_violations= red_light_camera_tickets_before_previous_lookup, speed_camera_violations=speed_camera_tickets_before_previous_lookup, state=state, username=previous_username) below_dvaa_thresholds = (dvaa_red_light_camera_max_streak < 5 and dvaa_speed_camera_max_streak < 15) new_camera_violations_exist = ( red_light_camera_tickets_after_previous_lookup + speed_camera_tickets_after_previous_lookup) > 0 if use_dvaa_thresholds and below_dvaa_thresholds: # We don't expect a lookup here. expect_response = False plate_lookups = [] elif use_only_visible_tweets and not can_link_tweet: # We don't expect a lookup here. expect_response = False plate_lookups = [plate_lookup_to_return] elif not new_camera_violations_exist: # We don't expect a lookup here. expect_response = False plate_lookups = [plate_lookup_to_return] elif dry_run: # We don't expect a lookup here. expect_response = False plate_lookups = [plate_lookup_to_return] else: expect_response = True plate_lookups = [plate_lookup_to_return] mocked_plate_lookup_get_all_in.return_value = plate_lookups open_data_plate_lookup_before_previous_lookup = OpenDataServicePlateLookup( boroughs=[], camera_streak_data={ 'Failure to Stop at Red Light': CameraStreakData( min_streak_date=dvaa_red_light_camera_min_streak_date, max_streak=dvaa_red_light_camera_max_streak, max_streak_date=dvaa_red_light_camera_max_streak_date), 'Mixed': CameraStreakData(min_streak_date=rdaa_min_streak_date, max_streak=rdaa_max_streak, max_streak_date=rdaa_max_streak_date), 'School Zone Speed Camera Violation': CameraStreakData( min_streak_date=dvaa_speed_camera_min_streak_date, max_streak=dvaa_speed_camera_max_streak, max_streak_date=dvaa_speed_camera_max_streak_date), }, fines=FineData(), num_violations=150, plate=plate, plate_types=plate_types, state=state, violations=[{ 'count': red_light_camera_tickets_before_previous_lookup, 'title': 'Failure To Stop At Red Light' }, { 'count': speed_camera_tickets_before_previous_lookup, 'title': 'School Zone Speed Camera Violation' }], years=[]) open_data_response_before_previous_lookup = OpenDataServiceResponse( data=open_data_plate_lookup_before_previous_lookup, success=True) open_data_plate_lookup_after_previous_lookup = OpenDataServicePlateLookup( boroughs=[], camera_streak_data={ 'Failure to Stop at Red Light': CameraStreakData( min_streak_date=dvaa_red_light_camera_min_streak_date, max_streak=dvaa_red_light_camera_max_streak, max_streak_date=dvaa_red_light_camera_max_streak_date), 'Mixed': CameraStreakData(min_streak_date=rdaa_min_streak_date, max_streak=rdaa_max_streak, max_streak_date=rdaa_max_streak_date), 'School Zone Speed Camera Violation': CameraStreakData( min_streak_date=dvaa_speed_camera_min_streak_date, max_streak=dvaa_speed_camera_max_streak, max_streak_date=dvaa_speed_camera_max_streak_date), }, fines=FineData(), num_violations=150, plate=plate, plate_types=plate_types, state=state, violations=[{ 'count': red_light_camera_tickets_after_previous_lookup, 'title': 'Failure To Stop At Red Light' }, { 'count': speed_camera_tickets_after_previous_lookup, 'title': 'School Zone Speed Camera Violation' }], years=[]) open_data_response_after_previous_lookup = OpenDataServiceResponse( data=open_data_plate_lookup_after_previous_lookup, success=True) mocked_open_data_service_look_up_vehicle.side_effect = [ open_data_response_before_previous_lookup, open_data_response_after_previous_lookup ] can_link_tweet_string = ( f' by @BarackObama: ' f'https://twitter.com/BarackObama/status/{previous_message_id}' if can_link_tweet else '') red_light_camera_tickets_diff_string = ( f'{red_light_camera_tickets_after_previous_lookup} | Red Light Camera Violations\n' if red_light_camera_tickets_after_previous_lookup else '') speed_camera_tickets_diff_string = ( f'{speed_camera_tickets_after_previous_lookup} | Speed Safety Camera Violations\n' if speed_camera_tickets_after_previous_lookup else '') summary_string = ( f'#NY_ABCDEFG was originally queried on January 3, 2020 at 09:37AM' f'{can_link_tweet_string}.') update_string = ( f'From November 26, 2018 to November 22, 2019, this vehicle ' f'received 20 camera violations. Over the past 12 months, this ' f'vehicle received {new_camera_violations_string}: \n\n' f'{red_light_camera_tickets_diff_string}' f'{speed_camera_tickets_diff_string}') job.run(is_dry_run=dry_run, use_only_visible_tweets=use_only_visible_tweets) if expect_response: mocked_traffic_violations_tweeter().send_status.assert_called_with( message_parts=[summary_string, update_string], on_error_message=(f'Error printing reckless driver update. ' f'Tagging @bdhowald.')) else: mocked_traffic_violations_tweeter().send_status.assert_not_called()
def test_print_reckless_driver_retrospective(self, mocked_tweet_detection_service_tweet_exists, mocked_traffic_violations_tweeter, mocked_plate_lookup_get_all_in, mocked_plate_lookup_query, mocked_open_data_service_look_up_vehicle, can_link_tweet=False, dry_run=False, new_camera_violations_string='10 new camera violations', red_light_camera_tickets_after_previous_lookup=3, speed_camera_tickets_after_previous_lookup=7, red_light_camera_tickets_before_previous_lookup=8, speed_camera_tickets_before_previous_lookup=12): job = RecklessDriverRetrospectiveJob() # mocked_plate_lookup_query.session.query( # ).filter().group_by().all.return_value = [1,2,3] mocked_tweet_detection_service_tweet_exists.return_value = can_link_tweet plate = 'ABCDEFG' plate_types = 'COM,PAS' state = 'NY' now = datetime.now() previous_message_id = random.randint(1000000000000000000, 2000000000000000000) previous_message_source = 'status' previous_num_tickets = 123 previous_username = '******' max_streak = 20 min_streak_date = datetime(2018, 11, 26, 0, 0, 0).strftime('%B %-d, %Y') max_streak_date = datetime(2019, 11, 22, 0, 0, 0).strftime('%B %-d, %Y') plate_lookups = [ PlateLookup( bus_lane_camera_violations=2, created_at=datetime(2020, 1, 3, 14, 37, 12), message_id=previous_message_id, message_source=previous_message_source, num_tickets=previous_num_tickets, plate=plate, plate_types=plate_types, red_light_camera_violations=red_light_camera_tickets_before_previous_lookup, speed_camera_violations=speed_camera_tickets_before_previous_lookup, state=state, username=previous_username)] mocked_plate_lookup_get_all_in.return_value = plate_lookups open_data_plate_lookup_before_previous_lookup = OpenDataServicePlateLookup( boroughs=[], camera_streak_data=CameraStreakData( min_streak_date=min_streak_date, max_streak=max_streak, max_streak_date=max_streak_date), fines=FineData(), num_violations=150, plate=plate, plate_types=plate_types, state=state, violations=[ {'count': red_light_camera_tickets_before_previous_lookup, 'title': 'Failure To Stop At Red Light'}, {'count': speed_camera_tickets_before_previous_lookup, 'title': 'School Zone Speed Camera Violation'}], years=[]) open_data_response_before_previous_lookup = OpenDataServiceResponse( data=open_data_plate_lookup_before_previous_lookup, success=True) open_data_plate_lookup_after_previous_lookup = OpenDataServicePlateLookup( boroughs=[], camera_streak_data=CameraStreakData( min_streak_date=min_streak_date, max_streak=max_streak, max_streak_date=max_streak_date), fines=FineData(), num_violations=150, plate=plate, plate_types=plate_types, state=state, violations=[ {'count': red_light_camera_tickets_after_previous_lookup, 'title': 'Failure To Stop At Red Light'}, {'count': speed_camera_tickets_after_previous_lookup, 'title': 'School Zone Speed Camera Violation'}], years=[]) open_data_response_after_previous_lookup = OpenDataServiceResponse( data=open_data_plate_lookup_after_previous_lookup, success=True) mocked_open_data_service_look_up_vehicle.side_effect = [ open_data_response_before_previous_lookup, open_data_response_after_previous_lookup] can_link_tweet_string = ( f' by @BarackObama: ' f'https://twitter.com/BarackObama/status/{previous_message_id}' if can_link_tweet else '') red_light_camera_tickets_diff_string = ( f'{red_light_camera_tickets_after_previous_lookup} | Red Light Camera Violations\n' if red_light_camera_tickets_after_previous_lookup else '') speed_camera_tickets_diff_string = ( f'{speed_camera_tickets_after_previous_lookup} | Speed Safety Camera Violations\n' if speed_camera_tickets_after_previous_lookup else '') summary_string = ( f'#NY_ABCDEFG was originally queried on January 3, 2020 at 09:37AM' f'{can_link_tweet_string}.') update_string = ( f'From November 26, 2018 to November 22, 2019, this vehicle ' f'received 20 camera violations. Over the past 12 months, this ' f'vehicle received {new_camera_violations_string}: \n\n' f'{red_light_camera_tickets_diff_string}' f'{speed_camera_tickets_diff_string}') reckless_string = ( f'Thank you to @bradlander for making the Dangerous Driver ' f'Abatement Act a reality.') job_should_be_run = (red_light_camera_tickets_after_previous_lookup + speed_camera_tickets_after_previous_lookup) > 0 job.run(is_dry_run=dry_run) if not dry_run and job_should_be_run: mocked_traffic_violations_tweeter().send_status.assert_called_with( message_parts=[summary_string, update_string, reckless_string], on_error_message=( f'Error printing reckless driver update. ' f'Tagging @bdhowald.'))