diff --git a/src/server/BreCal/validators/validation_rule_functions.py b/src/server/BreCal/validators/validation_rule_functions.py index 17e10dd..d6b6fbc 100644 --- a/src/server/BreCal/validators/validation_rule_functions.py +++ b/src/server/BreCal/validators/validation_rule_functions.py @@ -60,13 +60,13 @@ class ValidationRuleBaseFunctions(): def describe_error_message(self, key)->str: """ - Takes any error message, which typically is the validation rule's function name and returns a description of the error. + Takes any error message, which typically is the validation rule's function name and returns a description of the error. In case that the error code is not defined in self.error_message_dict, return the cryptic error code instead returns: string """ return self.error_message_dict.get(key,key) - + def get_no_violation_default_output(self): """return the default output of a validation function with no validation: a tuple of (GREEN state, None)""" return (StatusFlags.GREEN, None) @@ -75,7 +75,7 @@ class ValidationRuleBaseFunctions(): """ # base function for all validation rules in the group {0001} A-L - measures the time between NOW and query_time. + measures the time between NOW and query_time. When the query_time lays in the past, the delta is negative when the query_time lays in the future, the delta is positive @@ -93,11 +93,11 @@ class ValidationRuleBaseFunctions(): # rule is only applicable, when 'key_time' is not defined (neither None, nor pd.NaT) if (key_time is not None) and (key_time is not pd.NaT): return False - + # when query_time is not valid, the rule cannot be applied if self.check_is_not_a_time_or_is_none(query_time): return False - + # otherwise, this rule applies and the difference between 'now' and the query time is measured delta = self.time_logic.time_delta_from_now_to_tgt(tgt_time=query_time, unit="m") @@ -105,7 +105,7 @@ class ValidationRuleBaseFunctions(): # Violation, if delta <= threshold violation_state = (delta<=threshold) return violation_state - + def check_participants_agree_on_estimated_time(self, shipcall, query, df_times, applicable_shipcall_type)->bool: """ # base function for all validation rules in the group {0002} A-C @@ -116,11 +116,11 @@ class ValidationRuleBaseFunctions(): No violations are observed, when - the shipcall belongs to a different type than the rule expects - there are no matching times for the provided {query} (e.g., "eta_berth") - + Instead of comparing each individual result, this function counts the amount of unique instances. When there is not only one unique value, there are deviating time estimates, and a violation occurs - To reduce the potential of false violations, the agreement is rounded (e.g., by minute). + To reduce the potential of false violations, the agreement is rounded (e.g., by minute). returns: violation_state (bool) """ @@ -133,28 +133,31 @@ class ValidationRuleBaseFunctions(): if not self.ignore_port_administration_flag: participant_types = [ParticipantType.AGENCY.value, ParticipantType.MOORING.value, ParticipantType.PORT_ADMINISTRATION.value, ParticipantType.PILOT.value, ParticipantType.TUG.value] else: - participant_types = [ParticipantType.AGENCY.value, ParticipantType.MOORING.value, ParticipantType.PORT_ADMINISTRATION.value, ParticipantType.PILOT.value, ParticipantType.TUG.value] + participant_types = [ParticipantType.AGENCY.value, ParticipantType.MOORING.value, ParticipantType.PILOT.value, ParticipantType.TUG.value] df_times = df_times.loc[df_times["participant_type"].isin(participant_types),:] # exclude missing entries and consider only pd.Timestamp entries (which ignores pd.NaT/null entries) estimated_times = [time_ for time_ in df_times.loc[:,query].tolist() if isinstance(time_, pd.Timestamp)] # df_times = df_times.loc[~df_times[query].isnull(),:] - # apply rounding. For example, the agreement of different participants may be required to match minute-wise - # '15min' rounds to 'every 15 minutes'. E.g., '2023-09-22 08:18:49' becomes '2023-09-22 08:15:00' - estimated_times = [time_.round("15min") for time_ in estimated_times] - # when there are no entries left (no entries are provided), skip if len(estimated_times)==0: violation_state = False return violation_state + difference = np.max(estimated_times) - np.min(estimated_times) + violation_state = difference > pd.Timedelta("15min") + + # apply rounding. For example, the agreement of different participants may be required to match minute-wise + # '15min' rounds to 'every 15 minutes'. E.g., '2023-09-22 08:18:49' becomes '2023-09-22 08:15:00' + # estimated_times = [time_.round("15min") for time_ in estimated_times] + # there should only be one eta_berth, when all participants have provided the same time # this equates to the same criteria as checking, whether # times_agency.eta_berth==times_mooring.eta_berth==times_portadministration.eta_berth==times_pilot.eta_berth==times_tug.eta_berth - n_unique_times = len(np.unique(estimated_times)) - violation_state = n_unique_times!=1 + # n_unique_times = len(np.unique(estimated_times)) + # violation_state = n_unique_times!=1 return violation_state - + def check_unique_shipcall_counts(self, query:str, times_agency:pd.DataFrame, rounding="min", maximum_threshold=3, all_times_agency=None)->bool: """ # base function for all validation rules in the group {0005} A&B @@ -182,13 +185,13 @@ class ValidationRuleBaseFunctions(): class ValidationRuleFunctions(ValidationRuleBaseFunctions): """ - an accumulation object that makes sure, that any validation rule is translated to a function with default naming convention and + an accumulation object that makes sure, that any validation rule is translated to a function with default naming convention and return types. Each function should return a ValidationRuleState enumeration object and a description string to which validation rule the result belongs. These are returned as tuples (ValidationRuleState, validation_name) Each rule should have the same input arguments (self, shipcall, df_times, *args, **kwargs) The object makes heavy use of calls from an SQLHandler object, which provides functions for dataframe access and filtering. - + each validation_name is generated by calling the function inside a method validation_name = inspect.currentframe().f_code.co_name # validation_name then returns the name of the method from where 'currentframe()' was called. @@ -199,12 +202,12 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): """ def __init__(self, sql_handler): super().__init__(sql_handler) - return - + return + def get_validation_rule_functions(self): """return a list of all methods in this object, which are all validation rule functions.""" return [self.__getattribute__(mthd_) for mthd_ in dir(self) if ('validation_rule_fct' in mthd_) and (isinstance(self.__getattribute__(mthd_), types.MethodType))] - + def validation_rule_fct_missing_time_agency_berth_eta(self, shipcall, df_times, *args, **kwargs): """ Code: #0001-A @@ -213,17 +216,17 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): a certain threshold (e.g., 20 hours), a violation occurs 0001-A: - - Checks, if times_agency.eta_berth is filled in. + - Checks, if times_agency.eta_berth is filled in. - Measures the difference between 'now' and 'shipcall.eta'. """ if not shipcall.type in [ShipcallType.INCOMING.value]: return self.get_no_violation_default_output() - + # check, if the header is filled in unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY]) if unassigned: return self.get_no_violation_default_output() - + # preparation: obtain the correct times of the participant, define the query time and the key time times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) query_time = shipcall.eta @@ -236,7 +239,7 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): return (StatusFlags.YELLOW, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_missing_time_agency_berth_etd(self, shipcall, df_times, *args, **kwargs): """ Code: #0001-B @@ -245,17 +248,17 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): a certain threshold (e.g., 20 hours), a violation occurs 0001-B: - - Checks, if times_agency.etd_berth is filled in. + - Checks, if times_agency.etd_berth is filled in. - Measures the difference between 'now' and 'shipcall.etd'. """ if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]: return self.get_no_violation_default_output() - + # check, if the header is filled in unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY]) if unassigned: return self.get_no_violation_default_output() - + # preparation: obtain the correct times of the participant, define the query time and the key time times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) query_time = shipcall.etd @@ -268,7 +271,7 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): return (StatusFlags.YELLOW, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_missing_time_mooring_berth_eta(self, shipcall, df_times, *args, **kwargs): """ Code: #0001-C @@ -277,12 +280,12 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): a certain threshold (e.g., 20 hours), a violation occurs 0001-C: - - Checks, if times_mooring.eta_berth is filled in. + - Checks, if times_mooring.eta_berth is filled in. - Measures the difference between 'now' and 'times_agency.eta_berth'. """ if not shipcall.type in [ShipcallType.INCOMING.value]: return self.get_no_violation_default_output() - + # check, if the header is filled in unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.MOORING]) if unassigned: @@ -302,7 +305,7 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): return (StatusFlags.YELLOW, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_missing_time_mooring_berth_etd(self, shipcall, df_times, *args, **kwargs): """ Code: #0001-D @@ -311,12 +314,12 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): a certain threshold (e.g., 20 hours), a violation occurs 0001-D: - - Checks, if times_mooring.etd_berth is filled in. + - Checks, if times_mooring.etd_berth is filled in. - Measures the difference between 'now' and 'times_agency.etd_berth'. """ if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]: return self.get_no_violation_default_output() - + # check, if the header is filled in unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.MOORING]) if unassigned: @@ -336,7 +339,7 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): return (StatusFlags.YELLOW, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_missing_time_portadministration_berth_eta(self, shipcall, df_times, *args, **kwargs): """ Code: #0001-F @@ -345,20 +348,20 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): a certain threshold (e.g., 20 hours), a violation occurs 0001-F: - - Checks, if times_port_administration.eta_berth is filled in. + - Checks, if times_port_administration.eta_berth is filled in. - Measures the difference between 'now' and 'times_agency.eta_berth'. """ if self.ignore_port_administration_flag: return self.get_no_violation_default_output() - + if not shipcall.type in [ShipcallType.INCOMING.value]: return self.get_no_violation_default_output() - + # check, if the header is filled in unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.PORT_ADMINISTRATION]) if unassigned: return self.get_no_violation_default_output() - + # preparation: obtain the correct times of the participant, define the query time and the key time times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) times_port_administration = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.PORT_ADMINISTRATION.value) @@ -373,7 +376,7 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): return (StatusFlags.YELLOW, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_missing_time_portadministration_berth_etd(self, shipcall, df_times, *args, **kwargs): """ Code: #0001-G @@ -382,7 +385,7 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): a certain threshold (e.g., 20 hours), a violation occurs 0001-G: - - Checks, if times_port_administration.etd_berth is filled in. + - Checks, if times_port_administration.etd_berth is filled in. - Measures the difference between 'now' and 'times_agency.etd_berth'. """ if self.ignore_port_administration_flag: @@ -390,12 +393,12 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]: return self.get_no_violation_default_output() - + # check, if the header is filled in unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.PORT_ADMINISTRATION]) if unassigned: return self.get_no_violation_default_output() - + # preparation: obtain the correct times of the participant, define the query time and the key time # when there are no times, the function returns None times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) @@ -411,7 +414,7 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): return (StatusFlags.YELLOW, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_missing_time_pilot_berth_eta(self, shipcall, df_times, *args, **kwargs): """ Code: #0001-H @@ -420,17 +423,17 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): a certain threshold (e.g., 20 hours), a violation occurs 0001-H: - - Checks, if times_pilot.eta_berth is filled in. + - Checks, if times_pilot.eta_berth is filled in. - Measures the difference between 'now' and 'times_agency.eta_berth'. """ if not shipcall.type in [ShipcallType.INCOMING.value]: return self.get_no_violation_default_output() - + # check, if the header is filled in unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.PILOT]) if unassigned: return self.get_no_violation_default_output() - + # preparation: obtain the correct times of the participant, define the query time and the key time times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) times_pilot = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.PILOT.value) @@ -439,13 +442,13 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): key_time = times_pilot.eta_berth if times_pilot is not None else None threshold = ParticipantwiseTimeDelta.PILOT violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) - + if violation_state: validation_name = "validation_rule_fct_missing_time_pilot_berth_eta" return (StatusFlags.YELLOW, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_missing_time_pilot_berth_etd(self, shipcall, df_times, *args, **kwargs): """ Code: #0001-I @@ -454,17 +457,17 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): a certain threshold (e.g., 20 hours), a violation occurs 0001-I: - - Checks, if times_pilot.etd_berth is filled in. + - Checks, if times_pilot.etd_berth is filled in. - Measures the difference between 'now' and 'times_agency.etd_berth'. """ if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]: return self.get_no_violation_default_output() - + # check, if the header is filled in unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.PILOT]) if unassigned: return self.get_no_violation_default_output() - + # preparation: obtain the correct times of the participant, define the query time and the key time times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) times_pilot = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.PILOT.value) @@ -473,13 +476,13 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): key_time = times_pilot.etd_berth if times_pilot is not None else None threshold = ParticipantwiseTimeDelta.PILOT violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) - + if violation_state: validation_name = "validation_rule_fct_missing_time_pilot_berth_etd" return (StatusFlags.YELLOW, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_missing_time_tug_berth_eta(self, shipcall, df_times, *args, **kwargs): """ Code: #0001-J @@ -488,17 +491,17 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): a certain threshold (e.g., 20 hours), a violation occurs 0001-J: - - Checks, if times_tug.eta_berth is filled in. + - Checks, if times_tug.eta_berth is filled in. - Measures the difference between 'now' and 'times_agency.eta_berth'. """ if not shipcall.type in [ShipcallType.INCOMING.value]: return self.get_no_violation_default_output() - + # check, if the header is filled in unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.TUG]) if unassigned: return self.get_no_violation_default_output() - + # preparation: obtain the correct times of the participant, define the query time and the key time times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) times_tug = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TUG.value) @@ -507,13 +510,13 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): key_time = times_tug.eta_berth if times_tug is not None else None threshold = ParticipantwiseTimeDelta.TUG violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) - + if violation_state: validation_name = "validation_rule_fct_missing_time_tug_berth_eta" return (StatusFlags.YELLOW, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_missing_time_tug_berth_etd(self, shipcall, df_times, *args, **kwargs): """ Code: #0001-K @@ -522,17 +525,17 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): a certain threshold (e.g., 20 hours), a violation occurs 0001-K: - - Checks, if times_tug.etd_berth is filled in. + - Checks, if times_tug.etd_berth is filled in. - Measures the difference between 'now' and 'times_agency.etd_berth'. """ if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]: return self.get_no_violation_default_output() - + # check, if the header is filled in unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.TUG]) if unassigned: return self.get_no_violation_default_output() - + # preparation: obtain the correct times of the participant, define the query time and the key time times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) times_tug = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TUG.value) @@ -541,13 +544,13 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): key_time = times_tug.etd_berth if times_tug is not None else None threshold = ParticipantwiseTimeDelta.TUG violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) - + if violation_state: validation_name = "validation_rule_fct_missing_time_tug_berth_etd" return (StatusFlags.YELLOW, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_missing_time_terminal_berth_eta(self, shipcall, df_times, *args, **kwargs): """ Code: #0001-L @@ -556,17 +559,17 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): a certain threshold (e.g., 20 hours), a violation occurs 0001-L: - - Checks, if times_terminal.operations_start is filled in. + - Checks, if times_terminal.operations_start is filled in. - Measures the difference between 'now' and 'times_agency.eta_berth'. """ if not shipcall.type in [ShipcallType.INCOMING.value]: return self.get_no_violation_default_output() - + # check, if the header is filled in unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.TERMINAL]) if unassigned: return self.get_no_violation_default_output() - + # preparation: obtain the correct times of the participant, define the query time and the key time times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) times_terminal = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TERMINAL.value) @@ -575,13 +578,13 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): key_time = times_terminal.operations_start if times_terminal is not None else None # eta_berth does not exist in times_terminal! Instead, it is called operations_start threshold = ParticipantwiseTimeDelta.TERMINAL violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) - + if violation_state: validation_name = "validation_rule_fct_missing_time_terminal_berth_eta" return (StatusFlags.YELLOW, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_missing_time_terminal_berth_etd(self, shipcall, df_times, *args, **kwargs): """ Code: #0001-M @@ -590,17 +593,17 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): a certain threshold (e.g., 20 hours), a violation occurs 0001-M: - - Checks, if times_terminal.operations_end is filled in. + - Checks, if times_terminal.operations_end is filled in. - Measures the difference between 'now' and 'times_agency.etd_berth'. """ if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]: return self.get_no_violation_default_output() - + # check, if the header is filled in unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.TERMINAL]) if unassigned: return self.get_no_violation_default_output() - + # preparation: obtain the correct times of the participant, define the query time and the key time times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) times_terminal = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TERMINAL.value) @@ -609,13 +612,13 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): key_time = times_terminal.operations_end if times_terminal is not None else None # etd_berth does not exist in times_terminal! Instead, it is called operations_end threshold = ParticipantwiseTimeDelta.TERMINAL violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) - + if violation_state: validation_name = "validation_rule_fct_missing_time_terminal_berth_etd" return (StatusFlags.YELLOW, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_shipcall_incoming_participants_disagree_on_eta(self, shipcall, df_times, *args, **kwargs): """ Code: #0002-A @@ -624,21 +627,21 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): Filter: only applies to incoming shipcalls """ query = "eta_berth" - - violation_state = self.check_participants_agree_on_estimated_time( - shipcall = shipcall, - query=query, - df_times=df_times, + violation_state = self.check_participants_agree_on_estimated_time( + shipcall = shipcall, + + query=query, + df_times=df_times, applicable_shipcall_type=ShipcallType.INCOMING ) - + if violation_state: validation_name = "validation_rule_fct_shipcall_incoming_participants_disagree_on_eta" return (StatusFlags.RED, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd(self, shipcall, df_times, *args, **kwargs): """ Code: #0002-B @@ -647,12 +650,12 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): Filter: only applies to outgoing shipcalls """ query = "etd_berth" - - violation_state = self.check_participants_agree_on_estimated_time( - shipcall = shipcall, - query=query, - df_times=df_times, + violation_state = self.check_participants_agree_on_estimated_time( + shipcall = shipcall, + + query=query, + df_times=df_times, applicable_shipcall_type=ShipcallType.OUTGOING ) @@ -661,7 +664,7 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): return (StatusFlags.RED, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd(self, shipcall, df_times, *args, **kwargs): """ Code: #0002-C @@ -670,21 +673,21 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): Filter: only applies to shifting shipcalls """ violation_state_eta = self.check_participants_agree_on_estimated_time( - shipcall = shipcall, + shipcall = shipcall, - query="eta_berth", - df_times=df_times, + query="eta_berth", + df_times=df_times, applicable_shipcall_type=ShipcallType.SHIFTING ) - + violation_state_etd = self.check_participants_agree_on_estimated_time( - shipcall = shipcall, + shipcall = shipcall, - query="etd_berth", - df_times=df_times, + query="etd_berth", + df_times=df_times, applicable_shipcall_type=ShipcallType.SHIFTING ) - + # apply 'eta_berth' check # apply 'etd_berth' # violation: if either 'eta_berth' or 'etd_berth' is violated @@ -698,7 +701,7 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): return (StatusFlags.RED, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_eta_time_not_in_operation_window(self, shipcall, df_times, *args, **kwargs): """ Code: #0003-A @@ -710,14 +713,14 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): """ if not shipcall.type in [ShipcallType.INCOMING.value]: return self.get_no_violation_default_output() - + # check, if the header is filled in (agency & terminal) if len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) != 1: return self.get_no_violation_default_output() # rule not applicable if len(df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value]) != 1: return self.get_no_violation_default_output() # rule not applicable - + # get agency & terminal times times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) times_terminal = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TERMINAL.value) @@ -738,7 +741,7 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): return (StatusFlags.RED, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_etd_time_not_in_operation_window(self, shipcall, df_times, *args, **kwargs): """ Code: #0003-B @@ -750,14 +753,14 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): """ if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]: return self.get_no_violation_default_output() - + # check, if the header is filled in (agency & terminal) if len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) != 1: return self.get_no_violation_default_output() # rule not applicable if len(df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value]) != 1: return self.get_no_violation_default_output() # rule not applicable - + # get agency & terminal times times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) times_terminal = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TERMINAL.value) @@ -778,7 +781,7 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): return (StatusFlags.RED, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_eta_time_not_in_tidal_window(self, shipcall, df_times, *args, **kwargs): """ Code: #0004-A @@ -790,7 +793,7 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): """ if not shipcall.type in [ShipcallType.INCOMING.value]: return self.get_no_violation_default_output() - + # check, if the header is filled in (agency) if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1: return self.get_no_violation_default_output() @@ -809,7 +812,7 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): return (StatusFlags.RED, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_etd_time_not_in_tidal_window(self, shipcall, df_times, *args, **kwargs): """ Code: #0004-B @@ -821,7 +824,7 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): """ if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]: return self.get_no_violation_default_output() - + # check, if the header is filled in (agency) if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1: return self.get_no_violation_default_output() @@ -840,7 +843,7 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): return (StatusFlags.RED, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_too_many_identical_eta_times(self, shipcall, df_times, rounding = "min", maximum_threshold = 3, all_times_agency=None, *args, **kwargs): """ Code: #0005-A @@ -861,7 +864,7 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): return (StatusFlags.YELLOW, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_too_many_identical_etd_times(self, shipcall, df_times, rounding = "min", maximum_threshold = 3, all_times_agency=None, *args, **kwargs): """ Code: #0005-B @@ -882,7 +885,7 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): return (StatusFlags.YELLOW, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_agency_and_terminal_berth_id_disagreement(self, shipcall, df_times, *args, **kwargs): """ Code: #0006-A @@ -895,18 +898,18 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): if len(df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value]) == 0: return self.get_no_violation_default_output() # rule not applicable - + times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) times_terminal = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TERMINAL.value) # when one of the two values is null, the state is GREEN if (times_agency.berth_id is None) or (times_terminal.berth_id is None): return self.get_no_violation_default_output() - + # when one of the two values is null, the state is GREEN if (pd.isnull(times_agency.berth_id)) or (pd.isnull(times_terminal.berth_id)): return self.get_no_violation_default_output() - + if shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]: return self.get_no_violation_default_output() @@ -918,7 +921,7 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): return (StatusFlags.YELLOW, validation_name) else: return self.get_no_violation_default_output() - + def validation_rule_fct_agency_and_terminal_pier_side_disagreement(self, shipcall, df_times, *args, **kwargs): """ Code: #0006-B @@ -931,18 +934,18 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): if len(df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value]) == 0: return self.get_no_violation_default_output() # rule not applicable - + times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) times_terminal = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TERMINAL.value) # when one of the two values is null, the state is GREEN if (shipcall.pier_side is None) or (times_terminal.pier_side is None): return self.get_no_violation_default_output() - + # when one of the two values is null, the state is GREEN if (pd.isnull(shipcall.pier_side)) or (pd.isnull(times_terminal.pier_side)): return self.get_no_violation_default_output() - + # only incoming shipcalls matter. The other ones are not relevant for the pier_side selection if shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]: return self.get_no_violation_default_output()