git_brcal/src/server/BreCal/validators/validation_rule_functions.py

764 lines
39 KiB
Python

import inspect
import types
from BreCal.database.enums import ParticipantType, ShipcallType, ParticipantwiseTimeDelta
import numpy as np
import pandas as pd
from BreCal.validators.time_logic import TimeLogic
from BreCal.database.enums import StatusFlags
#from BreCal.validators.schema_validation import validation_state_and_validation_name
class ValidationRuleBaseFunctions():
"""
Base object with individual functions, which the {ValidationRuleFunctions}-child refers to.
This parent class provides base functions and helps to restructure the code in a more comprehensible way.
"""
def __init__(self, sql_handler):
self.sql_handler = sql_handler
self.time_logic = TimeLogic()
def check_time_delta_violation_query_time_to_now(self, query_time:pd.Timestamp, key_time:pd.Timestamp, threshold:float)->bool:
"""
# base function for all validation rules in the group {0001} A-L
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
returns a violation state depending on whether the delta is
Violation, if: 0 >= delta > threshold
When the key time is defined (not None), there is no violation. Returns False
options:
query_time: will be used to measure the time difference of 'now' until the query time
key_time: will be used to check, whether the respective key already has a value
threshold: threshold where a time difference becomes crucial. When the delta is below the threshold, a violation might occur
"""
# rule is not applicable -> return 'GREEN'
if key_time is not None:
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")
# a violation occurs, when the delta (in minutes) exceeds the specified threshold of a participant
# to prevent past-events from triggering violations, negative values are ignored
# Violation, if 0 >= delta >= threshold
violation_state = (delta >= 0) and (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
compares, whether the participants agree on the estimated time (of arrival or departure), depending on
whether the shipcall type is incoming, outgoing or shifting.
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
returns: violation_state (bool)
"""
# shipcall type filter: consider only shipcalls, where the type matches
if shipcall.type != applicable_shipcall_type.value:
violation_state = False
return violation_state
# filter by participant types of interest (agency, mooring, portauthority/administration, pilot, tug)
participant_types = [ParticipantType.AGENCY.value, ParticipantType.MOORING.value, ParticipantType.PORT_ADMINISTRATION.value, ParticipantType.PILOT.value, ParticipantType.TUG.value]
df_times = df_times.loc[df_times["participant_type"].isin(participant_types),:]
# exclude missing entries
df_times.loc[~df_times[query].isnull(),:]
# when there are no entries left (no entries are provided), skip
if len(df_times)==0:
violation_state = False
return violation_state
# 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
unique_times = len(pd.unique(df_times.loc[:,query]))
violation_state = unique_times!=1
return violation_state
def check_unique_shipcall_counts(self, query:str, rounding="min", maximum_threshold=3)->bool:
"""
# base function for all validation rules in the group {0005} A&B
compares how many unique times are found for the provided {query} (e.g., "eta_berth")
This function rounds the results, counts the unique values and returns a boolean state, whether the {maximum_threshold} is exceeded
"""
# filter the df: keep only times_agents
# filter out all NaN and NaT entries
times_agency = self.sql_handler.get_times_for_agency(non_null_column=query)
# get values and optionally round the values
(values, unique, counts) = self.sql_handler.get_unique_ship_counts(all_df_times=times_agency, query=query, rounding=rounding, maximum_threshold=maximum_threshold)
# when ANY of the unique values exceeds the threshold, a violation is observed
violation_state = np.any(np.greater(counts, maximum_threshold))
return violation_state
class ValidationRuleFunctions(ValidationRuleBaseFunctions):
"""
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.
# example:
#def validation_rule_fct_example(self, shipcall, df_times):
#validation_name = inspect.currentframe().f_code.co_name
#return (ValidationRuleState.NONE, validation_name)
"""
def __init__(self, sql_handler):
super().__init__(sql_handler)
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
Type: Local Rule
Description: this validation checks, whether there is a missing time. When the difference between an event (e.g., the shipcall eta) is below
a certain threshold (e.g., 20 hours), a violation occurs
0001-A:
- Checks, if times_agency.eta_berth is filled in.
- Measures the difference between 'now' and 'shipcall.eta'.
"""
# check, if the header is filled in (agency)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1:
return (StatusFlags.GREEN, None)
# 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
key_time = times_agency.eta_berth
threshold = ParticipantwiseTimeDelta.AGENCY
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 = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_agency_berth_etd(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-B
Type: Local Rule
Description: this validation checks, whether there is a missing time. When the difference between an event (e.g., the shipcall eta) is below
a certain threshold (e.g., 20 hours), a violation occurs
0001-B:
- Checks, if times_agency.etd_berth is filled in.
- Measures the difference between 'now' and 'shipcall.etd'.
"""
# check, if the header is filled in (agency)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1:
return (StatusFlags.GREEN, None)
# 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
key_time = times_agency.etd_berth
threshold = ParticipantwiseTimeDelta.AGENCY
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 = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_mooring_berth_eta(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-C
Type: Local Rule
Description: this validation checks, whether there is a missing time. When the difference between an event (e.g., the shipcall eta) is below
a certain threshold (e.g., 20 hours), a violation occurs
0001-C:
- Checks, if times_mooring.eta_berth is filled in.
- Measures the difference between 'now' and 'times_agency.eta_berth'.
"""
# check, if the header is filled in (agency & MOORING)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.MOORING.value])]) != 2:
return (StatusFlags.GREEN, None)
# 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_mooring = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.MOORING.value)
query_time = times_agency.eta_berth
key_time = times_mooring.eta_berth
threshold = ParticipantwiseTimeDelta.MOORING
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 = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_mooring_berth_etd(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-D
Type: Local Rule
Description: this validation checks, whether there is a missing time. When the difference between an event (e.g., the shipcall eta) is below
a certain threshold (e.g., 20 hours), a violation occurs
0001-D:
- Checks, if times_mooring.etd_berth is filled in.
- Measures the difference between 'now' and 'times_agency.etd_berth'.
"""
# check, if the header is filled in (agency & MOORING)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.MOORING.value])]) != 2:
return (StatusFlags.GREEN, None)
# 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_mooring = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.MOORING.value)
query_time = times_agency.etd_berth
key_time = times_mooring.etd_berth
threshold = ParticipantwiseTimeDelta.MOORING
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 = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_portadministration_berth_eta(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-F
Type: Local Rule
Description: this validation checks, whether there is a missing time. When the difference between an event (e.g., the shipcall eta) is below
a certain threshold (e.g., 20 hours), a violation occurs
0001-F:
- Checks, if times_port_administration.eta_berth is filled in.
- Measures the difference between 'now' and 'times_agency.eta_berth'.
"""
# check, if the header is filled in (agency & PORT_ADMINISTRATION)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.PORT_ADMINISTRATION.value])]) != 2:
return (StatusFlags.GREEN, None)
# 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)
query_time = times_agency.eta_berth
key_time = times_port_administration.eta_berth
threshold = ParticipantwiseTimeDelta.PORT_ADMINISTRATION
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 = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_portadministration_berth_etd(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-G
Type: Local Rule
Description: this validation checks, whether there is a missing time. When the difference between an event (e.g., the shipcall eta) is below
a certain threshold (e.g., 20 hours), a violation occurs
0001-G:
- Checks, if times_port_administration.etd_berth is filled in.
- Measures the difference between 'now' and 'times_agency.etd_berth'.
"""
# check, if the header is filled in (agency & PORT_ADMINISTRATION)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.PORT_ADMINISTRATION.value])]) != 2:
return (StatusFlags.GREEN, None)
# 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)
query_time = times_agency.etd_berth
key_time = times_port_administration.etd_berth
threshold = ParticipantwiseTimeDelta.PORT_ADMINISTRATION
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 = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_pilot_berth_eta(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-H
Type: Local Rule
Description: this validation checks, whether there is a missing time. When the difference between an event (e.g., the shipcall eta) is below
a certain threshold (e.g., 20 hours), a violation occurs
0001-H:
- Checks, if times_pilot.eta_berth is filled in.
- Measures the difference between 'now' and 'times_agency.eta_berth'.
"""
# check, if the header is filled in (agency & PILOT)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.PILOT.value])]) != 2:
return (StatusFlags.GREEN, None)
# 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)
query_time = times_agency.eta_berth
key_time = times_pilot.eta_berth
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 = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_pilot_berth_etd(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-I
Type: Local Rule
Description: this validation checks, whether there is a missing time. When the difference between an event (e.g., the shipcall eta) is below
a certain threshold (e.g., 20 hours), a violation occurs
0001-I:
- Checks, if times_pilot.etd_berth is filled in.
- Measures the difference between 'now' and 'times_agency.etd_berth'.
"""
# check, if the header is filled in (agency & PILOT)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.PILOT.value])]) != 2:
return (StatusFlags.GREEN, None)
# 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)
query_time = times_agency.etd_berth
key_time = times_pilot.etd_berth
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 = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_tug_berth_eta(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-J
Type: Local Rule
Description: this validation checks, whether there is a missing time. When the difference between an event (e.g., the shipcall eta) is below
a certain threshold (e.g., 20 hours), a violation occurs
0001-J:
- Checks, if times_tug.eta_berth is filled in.
- Measures the difference between 'now' and 'times_agency.eta_berth'.
"""
# check, if the header is filled in (agency & TUG)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TUG.value])]) != 2:
return (StatusFlags.GREEN, None)
# 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)
query_time = times_agency.eta_berth
key_time = times_tug.eta_berth
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 = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_tug_berth_etd(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-K
Type: Local Rule
Description: this validation checks, whether there is a missing time. When the difference between an event (e.g., the shipcall eta) is below
a certain threshold (e.g., 20 hours), a violation occurs
0001-K:
- Checks, if times_tug.etd_berth is filled in.
- Measures the difference between 'now' and 'times_agency.etd_berth'.
"""
# check, if the header is filled in (agency & TUG)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TUG.value])]) != 2:
return (StatusFlags.GREEN, None)
# 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)
query_time = times_agency.etd_berth
key_time = times_tug.etd_berth
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 = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_terminal_berth_eta(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-L
Type: Local Rule
Description: this validation checks, whether there is a missing time. When the difference between an event (e.g., the shipcall eta) is below
a certain threshold (e.g., 20 hours), a violation occurs
0001-L:
- Checks, if times_terminal.eta_berth is filled in.
- Measures the difference between 'now' and 'times_agency.eta_berth'.
"""
# check, if the header is filled in (agency & terminal)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TERMINAL.value])]) != 2:
return (StatusFlags.GREEN, None)
# 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)
query_time = times_agency.eta_berth
key_time = times_terminal.eta_berth
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 = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_terminal_berth_etd(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-K
Type: Local Rule
Description: this validation checks, whether there is a missing time. When the difference between an event (e.g., the shipcall eta) is below
a certain threshold (e.g., 20 hours), a violation occurs
0001-K:
- Checks, if times_terminal.etd_berth is filled in.
- Measures the difference between 'now' and 'times_agency.etd_berth'.
"""
# check, if the header is filled in (agency & terminal)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TERMINAL.value])]) != 2:
return (StatusFlags.GREEN, None)
# 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)
query_time = times_agency.etd_berth
key_time = times_terminal.etd_berth
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 = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_shipcall_incoming_participants_disagree_on_eta(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0002-A
Type: Local Rule
Description: this validation checks, whether the participants expect different ETA times
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,
applicable_shipcall_type=ShipcallType.INCOMING
)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.RED, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0002-B
Type: Local Rule
Description: this validation checks, whether the participants expect different ETA times
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,
applicable_shipcall_type=ShipcallType.OUTGOING
)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.RED, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0002-C
Type: Local Rule
Description: this validation checks, whether the participants expect different ETA or ETD times
Filter: only applies to shifting shipcalls
"""
violation_state_eta = self.check_participants_agree_on_estimated_time(
shipcall = shipcall,
query="eta_berth",
df_times=df_times,
applicable_shipcall_type=ShipcallType.SHIFTING
)
violation_state_etd = self.check_participants_agree_on_estimated_time(
shipcall = shipcall,
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
# functionally, this is the same as individually comparing all times for the participants
# times_agency.eta_berth==times_mooring.eta_berth==times_portadministration.eta_berth==times_pilot.eta_berth==times_tug.eta_berth
# times_agency.etd_berth==times_mooring.etd_berth==times_portadministration.etd_berth==times_pilot.etd_berth==times_tug.etd_berth
violation_state = (violation_state_eta) or (violation_state_etd)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.RED, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_eta_time_not_in_operation_window(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0003-A
Type: Local Rule
Description: this validation checks, whether the ETA time is between the provided operations window of the terminal
query time: eta_berth (times_agency)
start_time & end_time: operations_start & operations_end (times_terminal)
"""
# check, if the header is filled in (agency & terminal)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TERMINAL.value])]) != 2:
return (StatusFlags.GREEN, None)
# 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)
if (times_terminal.operations_end is pd.NaT) or (times_agency.etd_berth is pd.NaT):
return (StatusFlags.GREEN, None)
# check, whether the start of operations is AFTER the estimated arrival time
violation_state = times_terminal.operations_start<times_agency.eta_berth
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.RED, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_eta_time_not_in_operation_window(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0003-B
Type: Local Rule
Description: this validation checks, whether the ETD time is between the provided operations window of the terminal
query time: eta_berth (times_agency)
start_time & end_time: operations_start & operations_end (times_terminal)
"""
# check, if the header is filled in (agency & terminal)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TERMINAL.value])]) != 2:
return (StatusFlags.GREEN, None)
# 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)
if (times_terminal.operations_end is pd.NaT) or (times_agency.etd_berth is pd.NaT):
return (StatusFlags.GREEN, None)
# check, whether the end of operations is AFTER the estimated departure time
violation_state = times_terminal.operations_end > times_agency.etd_berth
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.RED, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_eta_time_not_in_tidal_window(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0004-A
Type: Local Rule
Description: this validation checks, whether the ETA time is between the provided tidal window
query time: eta_berth (times_agency)
start_time & end_time: tidal_window_from & tidal_window_to (shipcall)
"""
# check, if the header is filled in (agency)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1:
return (StatusFlags.GREEN, None)
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
# requirements: tidal window (from & to) is filled in
if (shipcall.tidal_window_from is pd.NaT) or (shipcall.tidal_window_to is pd.NaT) or (df_times.eta_berth is pd.NaT):
return (StatusFlags.GREEN, None)
# check, whether the query time is between start & end time
# a violation is observed, when the is NOT between start & end
violation_state = not self.time_logic.time_inbetween(query_time=times_agency.eta_berth, start_time=shipcall.tidal_window_from, end_time=shipcall.tidal_window_to)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.RED, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_etd_time_not_in_tidal_window(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0004-B
Type: Local Rule
Description: this validation checks, whether the ETD time is between the provided tidal window
query time: eta_berth (times_agency)
start_time & end_time: tidal_window_from & tidal_window_to (shipcall)
"""
# check, if the header is filled in (agency)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1:
return (StatusFlags.GREEN, None)
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
# requirements: tidal window (from & to) is filled in
if (shipcall.tidal_window_from is pd.NaT) or (shipcall.tidal_window_to is pd.NaT) or (df_times.eta_berth is pd.NaT):
return (StatusFlags.GREEN, None)
# check, whether the query time is between start & end time
# a violation is observed, when the is NOT between start & end
violation_state = not self.time_logic.time_inbetween(query_time=times_agency.etd_berth, start_time=shipcall.tidal_window_from, end_time=shipcall.tidal_window_to)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.RED, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_too_many_identical_eta_times(self, shipcall, df_times, rounding = "min", maximum_threshold = 3, *args, **kwargs):
"""
Code: #0005-A
Type: Global Rule
Description: this validation rule checks, whether there are too many shipcalls with identical ETA times.
"""
# when ANY of the unique values exceeds the threshold, a violation is observed
query = "eta_berth"
violation_state = self.check_unique_shipcall_counts(query, rounding=rounding, maximum_threshold=maximum_threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_too_many_identical_etd_times(self, shipcall, df_times, rounding = "min", maximum_threshold = 3, *args, **kwargs):
"""
Code: #0005-B
Type: Global Rule
Description: this validation rule checks, whether there are too many shipcalls with identical ETD times.
"""
# when ANY of the unique values exceeds the threshold, a violation is observed
query = "etd_berth"
violation_state = self.check_unique_shipcall_counts(query, rounding=rounding, maximum_threshold=maximum_threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_agency_and_terminal_berth_id_disagreement(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0006-A
Type: Local Rule
Description: This validation rule checks, whether agency and terminal agree with their designated berth place by checking berth_id.
"""
# check, if the header is filled in (agency & terminal)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TERMINAL.value])]) != 2:
return (StatusFlags.GREEN, None)
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)
violation_state = times_agency.berth_id!=times_terminal.berth_id
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_agency_and_terminal_pier_side_disagreement(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0006-B
Type: Local Rule
Description: This validation rule checks, whether agency and terminal agree with their designated pier side by checking pier_side.
"""
# check, if the header is filled in (agency & terminal)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TERMINAL.value])]) != 2:
return (StatusFlags.GREEN, None)
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)
violation_state = times_agency.pier_side!=times_terminal.pier_side
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)