01234567890123456789012345678901234567890123456789012345678901234567890123456789
451452453454455456457458459460461462463464465466467468469470 471472473474475476477478479480481482483484485486487488489490 26672668266926702671267226732674267526762677267826792680268126822683268426852686 26872688268926902691269226932694269526962697269826992700270127022703270427052706 288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929 3926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980 64336434643564366437643864396440644164426443644464456446644764486449645064516452 64536454645564566457645864596460646164626463646464656466646764686469647064716472 | <----SKIPPED LINES----> AIRCRAFT_LENGTH['Gulfstream Aerospace Gulfstream V (twin-jet)'] = 29.4 AIRCRAFT_LENGTH['Hawker Beechcraft 4000 (twin-jet)'] = 21.08 AIRCRAFT_LENGTH['Honda HondaJet (twin-jet)'] = 12.99 AIRCRAFT_LENGTH['IAI Gulfstream G100 (twin-jet)'] = 16.94 AIRCRAFT_LENGTH['IAI Gulfstream G200 (twin-jet)'] = 18.97 AIRCRAFT_LENGTH['IAI Gulfstream G280 (twin-jet)'] = 20.3 AIRCRAFT_LENGTH['Learjet 35 (twin-jet)'] = 14.83 AIRCRAFT_LENGTH['Learjet 45 (twin-jet)'] = 17.68 AIRCRAFT_LENGTH['Learjet 60 (twin-jet)'] = 17.88 AIRCRAFT_LENGTH['McDonnell Douglas MD-11 (tri-jet)'] = 61.6 AIRCRAFT_LENGTH['Pilatus PC-12 (single-turboprop)'] = 14.4 AIRCRAFT_LENGTH['Raytheon Hawker 800 (twin-jet)'] = 15.60 AIRCRAFT_LENGTH['Raytheon Hawker 800XP (twin-jet)'] = 15.60 AIRCRAFT_LENGTH['Raytheon Hawker 850XP (twin-jet)'] = 15.60 AIRCRAFT_LENGTH['Raytheon Hawker 1000 (twin-jet)'] = 16.08 AIRCRAFT_LENGTH['Rockwell Turbo Commander 690 (twin-turboprop)'] = 11.22 for mixed_case_plane in list(AIRCRAFT_LENGTH.keys()): # pylint: disable=C0201 AIRCRAFT_LENGTH[mixed_case_plane.upper()] = AIRCRAFT_LENGTH[mixed_case_plane] AIRCRAFT_LENGTH.pop(mixed_case_plane) def Log(message, file=None, rolling=None): """Write a message to a logfile along with a timestamp. Args: message: string message to write file: string representing file name and, if needed, path to the file to write to rolling: name of file that will keep only the last n files of file """ # can't define as a default parameter because LOGFILE name is potentially # modified based on SIMULATION flag if not file: file = LOGFILE # special case: for the main logfile, we always keep a rolling log if not rolling and file == LOGFILE: rolling = ROLLING_LOGFILE try: <----SKIPPED LINES----> extra_space = SPLITFLAP_CHARS_PER_LINE - sum([len(str(s)) for s in l]) last_gap = round(extra_space / (len(l) - 1)) return EvenlySpace([*l[:-2], str(l[-2]) + ' '*last_gap + str(l[-1])]) def RemoveParentheticals(s): """Removes all instances of () and the text contained within - from string.""" if not s: return s if '(' in s and ')' in s: open_paren = s.find('(') close_paren = s.find(')') else: return s if close_paren < open_paren: return s s = s.replace(s[open_paren:close_paren+1], '').strip().replace(' ', ' ') return RemoveParentheticals(s) def Ordinal(n): """Converts integer n to an ordinal string - i.e.: 2 -> 2nd; 5 -> 5th.""" return '%d%s' % (n, 'tsnrhtdd'[(math.floor(n/10)%10 != 1)*(n%10 < 4)*n%10::4]) def Screenify(lines, splitflap): """Transforms a list of lines to a single text string for display / print. Given a list of lines that is a fully-formed message to send to the splitflap display, this function transforms the list of strings to a single string that is an easier-to-read and more faithful representation of how the message will be displayed. The transformations are to add blank lines to the message to make it consistent number of lines, and to add border to the sides & top / bottom of the message. Args: lines: list of strings that comprise the message splitflap: boolean, True if directed for splitflap display; false if directed to screen <----SKIPPED LINES----> 1 + percent_size_difference)): last_aircraft_bigger = True comparative_text = 'smaller' last_flight_time_string = DisplayTime(last_flight, '%b %-d') if this_aircraft and last_aircraft: if this_aircraft_bigger or last_aircraft_bigger: message = ('%s used a %s plane today compared with last, on %s ' '(%s @ %dft vs. %s @ %dft)' % ( this_flight_number, comparative_text, last_flight_time_string, RemoveParentheticals(this_aircraft), this_aircraft_length*FEET_IN_METER, RemoveParentheticals(last_aircraft), last_aircraft_length*FEET_IN_METER)) elif last_aircraft and this_aircraft and last_aircraft != this_aircraft: message = ( '%s used a different aircraft today compared' ' with last, on %s (%s vs. %s)' % ( this_flight_number, last_flight_time_string, RemoveParentheticals(this_aircraft), RemoveParentheticals(last_aircraft))) return message def FlightInsightNthFlight(flights, hours=1, min_multiple_flights=2): """Generates string about seeing frequent flights to the same dest. Generates text of the following form for the "focus" flight in the data. - ASA1337 was the 4th flight to PHX in the last 53 minutes, served by Alaska Airlines, American Airlines, Southwest and United - SWA3102 was the 2nd flight to SAN in the last 25 minutes, both with Southwest Args: flights: the list of the raw data from which the insights will be generated, where the flights are listed in order of observation - i.e.: flights[0] was the earliest seen, and flights[-1] is the most recent flight for which we are attempting to generate an insight. hours: the time horizon over which to look for flights with the same destination. <----SKIPPED LINES----> overall_stats_elements = [] if delay_early_count: overall_stats_elements.append('%d ER' % delay_early_count) if delay_ontime_count: overall_stats_elements.append('%d OT' % delay_ontime_count) if delay_late_count: overall_stats_elements.append('%d LT' % delay_late_count) if delay_unknown_count: overall_stats_elements.append('%d UNK' % delay_unknown_count) overall_stats_text = '; '.join(overall_stats_elements) days_history = (int( round(last_timestamp - relevant_flights[0]['now']) / SECONDS_IN_DAY) + 1) late_percentage = delay_late_count / len(relevant_flights) if (superlative and delay_late_avg_sec >= min_average_delay_minutes * SECONDS_IN_MINUTE): message = ( 'This %s delay is the %s %s has seen in the ' 'last %d days (avg delay is %s); overall stats: %s' % ( SecondsToHhMm(this_delay_seconds), delay_keyword, this_flight_number, days_history, SecondsToHhMm(delay_late_avg_sec), overall_stats_text)) elif (late_percentage > min_late_percentage and delay_late_avg_sec >= min_average_delay_minutes * SECONDS_IN_MINUTE): # it's just been delayed frequently! message = ( 'With today''s delay of %s, %s is delayed %d%% of the time in' ' the last %d days for avg delay of %s; overall stats: %s' % ( SecondsToHhMm(this_delay_seconds), this_flight_number, int(100 * late_percentage), days_history, SecondsToHhMm(delay_late_avg_sec), overall_stats_text)) return message def FlightInsights(flights): """Identifies all the insight messages about the most recently seen flight. Generates a possibly-empty list of messages about the flight. Args: flights: List of all flights where the last flight in the list is the focus flight for which we are trying to identify something interesting. Returns: List of 2-tuples, where the first element in the tuple is a flag indicating <----SKIPPED LINES----> message_queue: FIFO list of message tuples of (message type, message string). next_message_time: epoch at which next message should be displayed configuration: dictionary of configuration attributes. screens: List of past screens displayed to splitflap screen. Returns: Next_message_time, potentially updated if a message has been displayed, or unchanged if no message was displayed. """ if message_queue and (time.time() >= next_message_time or SIMULATION): if SIMULATION: # drain the queue because the messages come so fast messages_to_display = list(message_queue) # passed by reference, so clear it out since we drained it to the display del message_queue[:] else: # display only one message, being mindful of the display timing messages_to_display = [message_queue.pop(0)] for message in messages_to_display: message_type = message[0] message_text = message[1] # There may be one or several insight messages that were added to the # message queue along with the flight at a time when the screen was # enabled, but by the time it comes to display them, the screen is now # disabled. These should not be displayed. Note that this check only # needs to be done for insight messages because other message types # are user initiated and so presumably should be displayed irrespective # of when the user triggered it to be displayed. if message_type == FLAG_MSG_INSIGHT and not MessageMeetsDisplayCriteria( configuration): Log('Message %s purged') else: if isinstance(message_text, str): message_text = textwrap.wrap( message_text, width=SPLITFLAP_CHARS_PER_LINE) display_message = Screenify(message_text, False) <----SKIPPED LINES----> |
01234567890123456789012345678901234567890123456789012345678901234567890123456789
451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501 267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722 290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945 3942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996 6449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491 | <----SKIPPED LINES----> AIRCRAFT_LENGTH['Gulfstream Aerospace Gulfstream V (twin-jet)'] = 29.4 AIRCRAFT_LENGTH['Hawker Beechcraft 4000 (twin-jet)'] = 21.08 AIRCRAFT_LENGTH['Honda HondaJet (twin-jet)'] = 12.99 AIRCRAFT_LENGTH['IAI Gulfstream G100 (twin-jet)'] = 16.94 AIRCRAFT_LENGTH['IAI Gulfstream G200 (twin-jet)'] = 18.97 AIRCRAFT_LENGTH['IAI Gulfstream G280 (twin-jet)'] = 20.3 AIRCRAFT_LENGTH['Learjet 35 (twin-jet)'] = 14.83 AIRCRAFT_LENGTH['Learjet 45 (twin-jet)'] = 17.68 AIRCRAFT_LENGTH['Learjet 60 (twin-jet)'] = 17.88 AIRCRAFT_LENGTH['McDonnell Douglas MD-11 (tri-jet)'] = 61.6 AIRCRAFT_LENGTH['Pilatus PC-12 (single-turboprop)'] = 14.4 AIRCRAFT_LENGTH['Raytheon Hawker 800 (twin-jet)'] = 15.60 AIRCRAFT_LENGTH['Raytheon Hawker 800XP (twin-jet)'] = 15.60 AIRCRAFT_LENGTH['Raytheon Hawker 850XP (twin-jet)'] = 15.60 AIRCRAFT_LENGTH['Raytheon Hawker 1000 (twin-jet)'] = 16.08 AIRCRAFT_LENGTH['Rockwell Turbo Commander 690 (twin-turboprop)'] = 11.22 for mixed_case_plane in list(AIRCRAFT_LENGTH.keys()): # pylint: disable=C0201 AIRCRAFT_LENGTH[mixed_case_plane.upper()] = AIRCRAFT_LENGTH[mixed_case_plane] AIRCRAFT_LENGTH.pop(mixed_case_plane) # pylint: disable=line-too-long SHORTER_AIRCRAFT_NAME = {} SHORTER_AIRCRAFT_NAME['Boeing 787-9 Dreamliner (twin-jet)'] = 'Boeing 787-9 (twin-jet)' SHORTER_AIRCRAFT_NAME['BOEING 787-10 Dreamliner (twin-jet)'] = 'Boeing 787-10 (twin-jet)' SHORTER_AIRCRAFT_NAME['Gulfstream Aerospace Gulfstream 3 (twin-jet)'] = 'Gulfstream 3 (twin-jet)' SHORTER_AIRCRAFT_NAME['Gulfstream Aerospace Gulfstream G450 (twin-jet)'] = 'Gulfstream G450 (twin-jet)' SHORTER_AIRCRAFT_NAME['Gulfstream Aerospace Gulfstream G550 (twin-jet)'] = 'Gulfstream G550 (twin-jet)' SHORTER_AIRCRAFT_NAME['Gulfstream Aerospace Gulfstream IV (twin-jet)'] = 'Gulfstream IV (twin-jet)' SHORTER_AIRCRAFT_NAME['Gulfstream Aerospace Gulfstream V (twin-jet)'] = 'Gulfstream V (twin-jet)' # pylint: enable=line-too-long def Log(message, file=None, rolling=None): """Write a message to a logfile along with a timestamp. Args: message: string message to write file: string representing file name and, if needed, path to the file to write to rolling: name of file that will keep only the last n files of file """ # can't define as a default parameter because LOGFILE name is potentially # modified based on SIMULATION flag if not file: file = LOGFILE # special case: for the main logfile, we always keep a rolling log if not rolling and file == LOGFILE: rolling = ROLLING_LOGFILE try: <----SKIPPED LINES----> extra_space = SPLITFLAP_CHARS_PER_LINE - sum([len(str(s)) for s in l]) last_gap = round(extra_space / (len(l) - 1)) return EvenlySpace([*l[:-2], str(l[-2]) + ' '*last_gap + str(l[-1])]) def RemoveParentheticals(s): """Removes all instances of () and the text contained within - from string.""" if not s: return s if '(' in s and ')' in s: open_paren = s.find('(') close_paren = s.find(')') else: return s if close_paren < open_paren: return s s = s.replace(s[open_paren:close_paren+1], '').strip().replace(' ', ' ') return RemoveParentheticals(s) def ShorterPlaneName(s): """Replaces full plane name with a shorter name, if it exists.""" return SHORTER_AIRCRAFT_NAME.get(s, s) def Ordinal(n): """Converts integer n to an ordinal string - i.e.: 2 -> 2nd; 5 -> 5th.""" return '%d%s' % (n, 'tsnrhtdd'[(math.floor(n/10)%10 != 1)*(n%10 < 4)*n%10::4]) def Screenify(lines, splitflap): """Transforms a list of lines to a single text string for display / print. Given a list of lines that is a fully-formed message to send to the splitflap display, this function transforms the list of strings to a single string that is an easier-to-read and more faithful representation of how the message will be displayed. The transformations are to add blank lines to the message to make it consistent number of lines, and to add border to the sides & top / bottom of the message. Args: lines: list of strings that comprise the message splitflap: boolean, True if directed for splitflap display; false if directed to screen <----SKIPPED LINES----> 1 + percent_size_difference)): last_aircraft_bigger = True comparative_text = 'smaller' last_flight_time_string = DisplayTime(last_flight, '%b %-d') if this_aircraft and last_aircraft: if this_aircraft_bigger or last_aircraft_bigger: message = ('%s used a %s plane today compared with last, on %s ' '(%s @ %dft vs. %s @ %dft)' % ( this_flight_number, comparative_text, last_flight_time_string, RemoveParentheticals(this_aircraft), this_aircraft_length*FEET_IN_METER, RemoveParentheticals(last_aircraft), last_aircraft_length*FEET_IN_METER)) elif last_aircraft and this_aircraft and last_aircraft != this_aircraft: message = ( '%s used a different aircraft today compared' ' with last, on %s (%s vs. %s)' % ( this_flight_number, last_flight_time_string, RemoveParentheticals(ShorterPlaneName(this_aircraft)), RemoveParentheticals(ShorterPlaneName(last_aircraft)))) return message def FlightInsightNthFlight(flights, hours=1, min_multiple_flights=2): """Generates string about seeing frequent flights to the same dest. Generates text of the following form for the "focus" flight in the data. - ASA1337 was the 4th flight to PHX in the last 53 minutes, served by Alaska Airlines, American Airlines, Southwest and United - SWA3102 was the 2nd flight to SAN in the last 25 minutes, both with Southwest Args: flights: the list of the raw data from which the insights will be generated, where the flights are listed in order of observation - i.e.: flights[0] was the earliest seen, and flights[-1] is the most recent flight for which we are attempting to generate an insight. hours: the time horizon over which to look for flights with the same destination. <----SKIPPED LINES----> overall_stats_elements = [] if delay_early_count: overall_stats_elements.append('%d ER' % delay_early_count) if delay_ontime_count: overall_stats_elements.append('%d OT' % delay_ontime_count) if delay_late_count: overall_stats_elements.append('%d LT' % delay_late_count) if delay_unknown_count: overall_stats_elements.append('%d UNK' % delay_unknown_count) overall_stats_text = '; '.join(overall_stats_elements) days_history = (int( round(last_timestamp - relevant_flights[0]['now']) / SECONDS_IN_DAY) + 1) late_percentage = delay_late_count / len(relevant_flights) if (superlative and delay_late_avg_sec >= min_average_delay_minutes * SECONDS_IN_MINUTE): message = ( 'This %s delay is the %s for %s of the ' 'last %d days (%s avg delay); overall: %s' % ( SecondsToHhMm(this_delay_seconds), delay_keyword, this_flight_number, days_history, SecondsToHhMm(delay_late_avg_sec), overall_stats_text)) elif (late_percentage > min_late_percentage and delay_late_avg_sec >= min_average_delay_minutes * SECONDS_IN_MINUTE): # it's just been delayed frequently! message = ( 'With today''s delay of %s, %s is delayed %d%% of the time in' ' the last %d days for avg %s delay; overall: %s' % ( SecondsToHhMm(this_delay_seconds), this_flight_number, int(100 * late_percentage), days_history, SecondsToHhMm(delay_late_avg_sec), overall_stats_text)) return message def FlightInsights(flights): """Identifies all the insight messages about the most recently seen flight. Generates a possibly-empty list of messages about the flight. Args: flights: List of all flights where the last flight in the list is the focus flight for which we are trying to identify something interesting. Returns: List of 2-tuples, where the first element in the tuple is a flag indicating <----SKIPPED LINES----> message_queue: FIFO list of message tuples of (message type, message string). next_message_time: epoch at which next message should be displayed configuration: dictionary of configuration attributes. screens: List of past screens displayed to splitflap screen. Returns: Next_message_time, potentially updated if a message has been displayed, or unchanged if no message was displayed. """ if message_queue and (time.time() >= next_message_time or SIMULATION): if SIMULATION: # drain the queue because the messages come so fast messages_to_display = list(message_queue) # passed by reference, so clear it out since we drained it to the display del message_queue[:] else: # display only one message, being mindful of the display timing messages_to_display = [message_queue.pop(0)] for message in messages_to_display: # we cannot just unpack the tuple because messages of type # FLAG_MSG_FLIGHT are 3-tuples (with the third element being the flight # dictionary) whereas other message types are 2-tuples message_type = message[0] message_text = message[1] # There may be one or several insight messages that were added to the # message queue along with the flight at a time when the screen was # enabled, but by the time it comes to display them, the screen is now # disabled. These should not be displayed. Note that this check only # needs to be done for insight messages because other message types # are user initiated and so presumably should be displayed irrespective # of when the user triggered it to be displayed. if message_type == FLAG_MSG_INSIGHT and not MessageMeetsDisplayCriteria( configuration): Log('Message %s purged') else: if isinstance(message_text, str): message_text = textwrap.wrap( message_text, width=SPLITFLAP_CHARS_PER_LINE) display_message = Screenify(message_text, False) <----SKIPPED LINES----> |