947 lines
53 KiB
Python
947 lines
53 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
|
|
|
|
# a human interpretable dictionary for error messages. In this case, the English language is preferred
|
|
error_message_dict = {
|
|
# 0001 A-M
|
|
"validation_rule_fct_missing_time_agency_berth_eta":"Shipcall arrives soon (<20 hours). The agency did not provide a time yet (ETA) {Rule #0001A}", # A
|
|
"validation_rule_fct_missing_time_agency_berth_etd":"Shipcall departs soon (<20 hours). The agency did not provide a time yet (ETD) {Rule #0001B}", # B
|
|
"validation_rule_fct_missing_time_mooring_berth_eta":"Shipcall arrives soon (<16 hours). The mooring did not provide a time yet (ETA) {Rule #0001C}", # C
|
|
"validation_rule_fct_missing_time_mooring_berth_etd":"Shipcall departs soon (<16 hours). The mooring did not provide a time yet (ETD) {Rule #0001D}", # D
|
|
"validation_rule_fct_missing_time_portadministration_berth_eta":"Shipcall arrives soon (<16 hours). The port administration did not provide a time yet (ETA) {Rule #0001F}", # F
|
|
"validation_rule_fct_missing_time_portadministration_berth_etd":"Shipcall departs soon (<16 hours). The port administration did not provide a time yet (ETD) {Rule #0001G}", # G
|
|
"validation_rule_fct_missing_time_pilot_berth_eta":"Shipcall arrives soon (<16 hours). The pilot did not provide a time yet (ETA) {Rule #0001H}", # H
|
|
"validation_rule_fct_missing_time_pilot_berth_etd":"Shipcall departs soon (<16 hours). The pilot did not provide a time yet (ETD) {Rule #0001I}", # I
|
|
"validation_rule_fct_missing_time_tug_berth_eta":"Shipcall arrives soon (<16 hours). The tugs did not provide a time yet (ETA) {Rule #0001J}", # J
|
|
"validation_rule_fct_missing_time_tug_berth_etd":"Shipcall departs soon (<16 hours). The tugs did not provide a time yet (ETD) {Rule #0001K}", # K
|
|
"validation_rule_fct_missing_time_terminal_berth_eta":"Shipcall arrives soon (<16 hours). The terminal did not provide a time yet (ETA) {Rule #0001L}", # L
|
|
"validation_rule_fct_missing_time_terminal_berth_etd":"Shipcall departs soon (<16 hours). The terminal did not provide a time yet (ETD) {Rule #0001M}", # M
|
|
|
|
# 0002 A+B+C
|
|
"validation_rule_fct_shipcall_incoming_participants_disagree_on_eta":"There are deviating times between agency, mooring, port authority, pilot and tug for the estimated time of arrival (ETA) {Rule #0002A}",
|
|
"validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd":"There are deviating times between agency, mooring, port authority, pilot and tug for the estimated time of departure (ETD) {Rule #0002B}",
|
|
"validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd":"There are deviating times between agency, mooring, port authority, pilot and tug for ETA and ETD {Rule #0002C}",
|
|
|
|
# 0003 A+B
|
|
"validation_rule_fct_eta_time_not_in_operation_window":"The estimated time of arrival will be AFTER the planned start of operations. {Rule #0003A}",
|
|
"validation_rule_fct_etd_time_not_in_operation_window":"The estimated time of departure is supposed to be AFTER the planned end of operations. {Rule #0003B}",
|
|
|
|
# 0004 A+B
|
|
"validation_rule_fct_eta_time_not_in_tidal_window":"The tidal window does not fit to the agency's estimated time of arrival (ETA) {Rule #0004A}",
|
|
"validation_rule_fct_etd_time_not_in_tidal_window":"The tidal window does not fit to the agency's estimated time of departure (ETD) {Rule #0004B}",
|
|
|
|
# 0005 A+B
|
|
"validation_rule_fct_too_many_identical_eta_times":"There are more than three ships with the same planned time of arrival (ETA) {Rule #0005A}",
|
|
"validation_rule_fct_too_many_identical_etd_times":"There are more than three ships with the same planned time of departure (ETD) {Rule #0005B}",
|
|
|
|
# 0006 A+B
|
|
"validation_rule_fct_agency_and_terminal_berth_id_disagreement":"Agency and Terminal are planning with different berths (the berth_id deviates). {Rule #0006A}",
|
|
"validation_rule_fct_agency_and_terminal_pier_side_disagreement":"Agency and Terminal are planning with different pier sides (the pier_side deviates). {Rule #0006B}",
|
|
}
|
|
|
|
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()
|
|
self.error_message_dict = error_message_dict
|
|
self.ignore_port_administration_flag = True # flag: turn off all port administration validation rules
|
|
|
|
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.
|
|
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)
|
|
|
|
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 (minutes)
|
|
"""
|
|
# rule is not applicable -> return 'GREEN'
|
|
# 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")
|
|
|
|
# a violation occurs, when the delta (in minutes) exceeds the specified threshold of a participant
|
|
# 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
|
|
|
|
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
|
|
|
|
To reduce the potential of false violations, the agreement is rounded (e.g., by minute).
|
|
|
|
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)
|
|
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]
|
|
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
|
|
|
|
# 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
|
|
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
|
|
|
|
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
|
|
if all_times_agency is None:
|
|
all_times_agency = self.sql_handler.get_times_for_agency(non_null_column=query)
|
|
|
|
# get values and optionally round the values (internally)
|
|
counts = self.sql_handler.get_unique_ship_counts(all_df_times=all_times_agency, times_agency=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
|
|
|
|
def check_is_not_a_time_or_is_none(self, value)->bool:
|
|
"""checks, if a provided value is either None or NaT"""
|
|
return (value is None) or (value is pd.NaT)
|
|
|
|
|
|
|
|
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'.
|
|
"""
|
|
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
|
|
key_time = times_agency.eta_berth if times_agency is not None else None
|
|
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 = "validation_rule_fct_missing_time_agency_berth_eta"
|
|
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
|
|
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'.
|
|
"""
|
|
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
|
|
key_time = times_agency.etd_berth if times_agency is not None else None
|
|
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 = "validation_rule_fct_missing_time_agency_berth_etd"
|
|
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
|
|
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'.
|
|
"""
|
|
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:
|
|
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_mooring = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.MOORING.value)
|
|
|
|
query_time = times_agency.eta_berth if times_agency is not None else None
|
|
key_time = times_mooring.eta_berth if times_mooring is not None else None
|
|
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 = "validation_rule_fct_missing_time_mooring_berth_eta"
|
|
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
|
|
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'.
|
|
"""
|
|
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:
|
|
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_mooring = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.MOORING.value)
|
|
|
|
query_time = times_agency.etd_berth if times_agency is not None else None
|
|
key_time = times_mooring.etd_berth if times_mooring is not None else None
|
|
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 = "validation_rule_fct_missing_time_mooring_berth_etd"
|
|
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
|
|
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'.
|
|
"""
|
|
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)
|
|
|
|
query_time = times_agency.eta_berth if times_agency is not None else None
|
|
key_time = times_port_administration.eta_berth if times_port_administration is not None else None
|
|
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 = "validation_rule_fct_missing_time_portadministration_berth_eta"
|
|
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
|
|
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'.
|
|
"""
|
|
if self.ignore_port_administration_flag:
|
|
return self.get_no_violation_default_output()
|
|
|
|
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)
|
|
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 if times_agency is not None else None
|
|
key_time = times_port_administration.etd_berth if times_port_administration is not None else None
|
|
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 = "validation_rule_fct_missing_time_portadministration_berth_etd"
|
|
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
|
|
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'.
|
|
"""
|
|
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)
|
|
|
|
query_time = times_agency.eta_berth if times_agency is not None else None
|
|
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
|
|
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'.
|
|
"""
|
|
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)
|
|
|
|
query_time = times_agency.etd_berth if times_agency is not None else None
|
|
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
|
|
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'.
|
|
"""
|
|
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)
|
|
|
|
query_time = times_agency.eta_berth if times_agency is not None else None
|
|
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
|
|
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'.
|
|
"""
|
|
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)
|
|
|
|
query_time = times_agency.etd_berth if times_agency is not None else None
|
|
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
|
|
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.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)
|
|
|
|
query_time = times_agency.eta_berth if times_agency is not None else None
|
|
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
|
|
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-M:
|
|
- 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)
|
|
|
|
query_time = times_agency.etd_berth if times_agency is not None else None
|
|
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
|
|
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 = "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
|
|
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 = "validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd"
|
|
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
|
|
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 = "validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd"
|
|
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
|
|
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)
|
|
"""
|
|
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)
|
|
|
|
if self.check_is_not_a_time_or_is_none(times_terminal.operations_start) or self.check_is_not_a_time_or_is_none(times_agency.eta_berth):
|
|
return self.get_no_violation_default_output()
|
|
|
|
# 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 = "validation_rule_fct_eta_time_not_in_operation_window"
|
|
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
|
|
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)
|
|
"""
|
|
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)
|
|
|
|
if self.check_is_not_a_time_or_is_none(times_terminal.operations_end) or self.check_is_not_a_time_or_is_none(times_agency.etd_berth):
|
|
return self.get_no_violation_default_output()
|
|
|
|
# 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 = "validation_rule_fct_etd_time_not_in_operation_window"
|
|
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
|
|
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)
|
|
"""
|
|
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()
|
|
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 self.check_is_not_a_time_or_is_none(shipcall.tidal_window_from) or self.check_is_not_a_time_or_is_none(shipcall.tidal_window_to) or self.check_is_not_a_time_or_is_none(times_agency.eta_berth): # 202310310: note: this should check times_agency, shouldn't it?
|
|
return self.get_no_violation_default_output()
|
|
|
|
# check, whether the query time is between start & end time
|
|
# a violation is observed, when the time 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 = "validation_rule_fct_eta_time_not_in_tidal_window"
|
|
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
|
|
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)
|
|
"""
|
|
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()
|
|
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 self.check_is_not_a_time_or_is_none(shipcall.tidal_window_from) or self.check_is_not_a_time_or_is_none(shipcall.tidal_window_to) or self.check_is_not_a_time_or_is_none(times_agency.etd_berth): # 202310310: note: this should check times_agency, shouldn't it?
|
|
return self.get_no_violation_default_output()
|
|
|
|
# check, whether the query time is between start & end time
|
|
# a violation is observed, when the time 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 = "validation_rule_fct_etd_time_not_in_tidal_window"
|
|
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
|
|
Type: Global Rule
|
|
Description: this validation rule checks, whether there are too many shipcalls with identical ETA times.
|
|
"""
|
|
times_agency = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]
|
|
# check, if the header is filled in (agency)
|
|
if len(times_agency) != 1:
|
|
return self.get_no_violation_default_output()
|
|
|
|
# when ANY of the unique values exceeds the threshold, a violation is observed
|
|
query = "eta_berth"
|
|
violation_state = self.check_unique_shipcall_counts(query, times_agency=times_agency, rounding=rounding, maximum_threshold=maximum_threshold, all_times_agency=all_times_agency)
|
|
|
|
if violation_state:
|
|
validation_name = "validation_rule_fct_too_many_identical_eta_times"
|
|
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
|
|
Type: Global Rule
|
|
Description: this validation rule checks, whether there are too many shipcalls with identical ETD times.
|
|
"""
|
|
times_agency = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]
|
|
# check, if the header is filled in (agency)
|
|
if len(times_agency) != 1:
|
|
return self.get_no_violation_default_output()
|
|
|
|
# when ANY of the unique values exceeds the threshold, a violation is observed
|
|
query = "etd_berth"
|
|
violation_state = self.check_unique_shipcall_counts(query, times_agency=times_agency, rounding=rounding, maximum_threshold=maximum_threshold, all_times_agency=all_times_agency)
|
|
|
|
if violation_state:
|
|
validation_name = "validation_rule_fct_too_many_identical_etd_times"
|
|
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
|
|
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"]==ParticipantType.AGENCY.value]) == 0:
|
|
return self.get_no_violation_default_output() # rule not applicable
|
|
|
|
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()
|
|
|
|
# only incoming shipcalls matter. The other ones are not relevant for the berth selection
|
|
violation_state = times_agency.berth_id!=times_terminal.berth_id
|
|
|
|
if violation_state:
|
|
validation_name = "validation_rule_fct_agency_and_terminal_berth_id_disagreement"
|
|
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
|
|
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"]==ParticipantType.AGENCY.value]) == 0:
|
|
return self.get_no_violation_default_output() # rule not applicable
|
|
|
|
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()
|
|
|
|
violation_state = bool(shipcall.pier_side)!=bool(times_terminal.pier_side)
|
|
|
|
if violation_state:
|
|
validation_name = "validation_rule_fct_agency_and_terminal_pier_side_disagreement"
|
|
return (StatusFlags.YELLOW, validation_name)
|
|
else:
|
|
return self.get_no_violation_default_output()
|
|
|
|
|