creating stub objects for every single validation function. Unit tests are created for each function to check whether they return 'GREEN' whenever no violation is expected, or 'YELLOW'/'RED' when a rule violation is artificially forced. The test framework now successfully runs 116 unit tests. Adapted some validation functions, applied refactoring and solved potential obstacles along the way. At least from the perspective of unit tests, every function now works as expected.
This commit is contained in:
parent
9d61b95378
commit
e9aace6268
@ -13,6 +13,25 @@ from .api import ships
|
|||||||
from .api import login
|
from .api import login
|
||||||
from .api import user
|
from .api import user
|
||||||
|
|
||||||
|
from BreCal.brecal_utils.file_handling import get_project_root, ensure_path
|
||||||
|
from BreCal.brecal_utils.test_handling import execute_test_with_pytest, execute_coverage_test
|
||||||
|
from BreCal.brecal_utils.time_handling import difference_to_then
|
||||||
|
|
||||||
|
from BreCal.validators.time_logic import TimeLogic
|
||||||
|
from BreCal.validators.validation_rules import ValidationRules
|
||||||
|
from BreCal.validators.schema_validation import validation_state_and_validation_name
|
||||||
|
|
||||||
|
from BreCal.stubs.times_agency import get_times_agency
|
||||||
|
from BreCal.stubs.times_bsmd import get_times_bsmd
|
||||||
|
from BreCal.stubs.times_mooring import get_times_mooring
|
||||||
|
from BreCal.stubs.times_pilot import get_times_pilot
|
||||||
|
from BreCal.stubs.times_portauthority import get_times_port_authority
|
||||||
|
from BreCal.stubs.times_terminal import get_times_terminal
|
||||||
|
from BreCal.stubs.times_tug import get_times_tug
|
||||||
|
from BreCal.stubs.times_full import get_times_full_simple
|
||||||
|
from BreCal.stubs.df_times import get_df_times
|
||||||
|
|
||||||
|
|
||||||
def create_app(test_config=None):
|
def create_app(test_config=None):
|
||||||
|
|
||||||
app = Flask(__name__, instance_relative_config=True)
|
app = Flask(__name__, instance_relative_config=True)
|
||||||
@ -47,22 +66,24 @@ def create_app(test_config=None):
|
|||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
from BreCal.brecal_utils.file_handling import get_project_root, ensure_path
|
|
||||||
from BreCal.brecal_utils.test_handling import execute_test_with_pytest, execute_coverage_test
|
|
||||||
from BreCal.brecal_utils.time_handling import difference_to_then
|
|
||||||
|
|
||||||
from BreCal.validators.time_logic import TimeLogic
|
|
||||||
from BreCal.validators.validation_rules import ValidationRules
|
|
||||||
from BreCal.validators.schema_validation import validation_state_and_validation_name
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"get_project_root",
|
"get_project_root",
|
||||||
"ensure_path",
|
"ensure_path",
|
||||||
"execute_test_with_pytest",
|
"execute_test_with_pytest",
|
||||||
"execute_coverage_test",
|
"execute_coverage_test",
|
||||||
"difference_to_then",
|
"difference_to_then",
|
||||||
"TimeLogic",
|
"TimeLogic",
|
||||||
"ValidationRules",
|
"ValidationRules",
|
||||||
"validation_state_and_validation_name",
|
"validation_state_and_validation_name",
|
||||||
|
|
||||||
|
"get_times_agency",
|
||||||
|
"get_times_bsmd",
|
||||||
|
"get_times_mooring",
|
||||||
|
"get_times_pilot",
|
||||||
|
"get_times_port_authority",
|
||||||
|
"get_times_terminal",
|
||||||
|
"get_times_tug",
|
||||||
|
"get_times_full_simple",
|
||||||
|
"get_df_times",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -221,6 +221,9 @@ class SQLHandler():
|
|||||||
|
|
||||||
# filter out all NaN and NaT entries
|
# filter out all NaN and NaT entries
|
||||||
if non_null_column is not None:
|
if non_null_column is not None:
|
||||||
|
# in the Pandas documentation, it says for .isnull():
|
||||||
|
# "This function takes a scalar or array-like object and indicates whether values are missing
|
||||||
|
# (NaN in numeric arrays, None or NaN in object arrays, NaT in datetimelike)."
|
||||||
df_times = df_times.loc[~df_times[non_null_column].isnull()] # NOT null filter
|
df_times = df_times.loc[~df_times[non_null_column].isnull()] # NOT null filter
|
||||||
|
|
||||||
# filter by the agency participant_type
|
# filter by the agency participant_type
|
||||||
@ -230,13 +233,20 @@ class SQLHandler():
|
|||||||
def filter_df_by_key_value(self, df, key, value)->pd.DataFrame:
|
def filter_df_by_key_value(self, df, key, value)->pd.DataFrame:
|
||||||
return df.loc[df[key]==value]
|
return df.loc[df[key]==value]
|
||||||
|
|
||||||
def get_unique_ship_counts(self, all_df_times:pd.DataFrame, query:str, rounding:str="min", maximum_threshold=3):
|
def get_unique_ship_counts(self, all_df_times:pd.DataFrame, times_agency:pd.DataFrame, query:str, rounding:str="min", maximum_threshold=3):
|
||||||
"""given a dataframe of all agency times, get all unique ship counts, their values (datetime) and the string tags. returns a tuple (values,unique,counts)"""
|
"""given a dataframe of all agency times, get all unique ship counts, their values (datetime) and the string tags. returns a tuple (values,unique,counts)"""
|
||||||
# get values and optional: rounding
|
# optional: rounding
|
||||||
values = all_df_times.loc[:, query]
|
|
||||||
if rounding is not None:
|
if rounding is not None:
|
||||||
values = values.dt.round(rounding) # e.g., 'min'
|
all_df_times.loc[:, query] = all_df_times.loc[:, query].dt.round(rounding) # e.g., 'min'
|
||||||
|
query_time_agency = times_agency[query].iloc[0].round(rounding)# e.g., 'min'
|
||||||
|
|
||||||
unique, counts = np.unique(values, return_counts=True)
|
# after rounding, filter {all_df_times}, so only those, which match the current query are of interest
|
||||||
violation_state = np.any(np.greater(counts, maximum_threshold))
|
# takes 'times_agency' to sample, which value should match
|
||||||
return (values, unique, counts)
|
all_df_times = all_df_times.loc[all_df_times[query]==query_time_agency]
|
||||||
|
|
||||||
|
# finally, count all remaining entries
|
||||||
|
values = all_df_times.loc[:, query]
|
||||||
|
|
||||||
|
# get unique entries and counts
|
||||||
|
counts = len(values) # unique, counts = np.unique(values, return_counts=True)
|
||||||
|
return counts # (values, unique, counts)
|
||||||
|
|||||||
@ -3,3 +3,4 @@ def generate_uuid1_int():
|
|||||||
"""# TODO: clarify, what kind of integer ID is used in mysql. Generates a proxy ID, which is used in the stubs"""
|
"""# TODO: clarify, what kind of integer ID is used in mysql. Generates a proxy ID, which is used in the stubs"""
|
||||||
from uuid import uuid1
|
from uuid import uuid1
|
||||||
return uuid1().int>>64
|
return uuid1().int>>64
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,6 @@ def get_berth_simple():
|
|||||||
|
|
||||||
# Note: #TODO: name, participant_id & lock state are arbitrary
|
# Note: #TODO: name, participant_id & lock state are arbitrary
|
||||||
name = "Avangard Dalben"
|
name = "Avangard Dalben"
|
||||||
participant_id = 1# e.g., Avangard
|
|
||||||
lock = False
|
lock = False
|
||||||
owner_id = 1 # e.g., Avangard
|
owner_id = 1 # e.g., Avangard
|
||||||
authority_id = 1 # e.g., Avangard
|
authority_id = 1 # e.g., Avangard
|
||||||
@ -19,7 +18,6 @@ def get_berth_simple():
|
|||||||
berth = Berth(
|
berth = Berth(
|
||||||
berth_id,
|
berth_id,
|
||||||
name,
|
name,
|
||||||
participant_id,
|
|
||||||
lock,
|
lock,
|
||||||
owner_id,
|
owner_id,
|
||||||
authority_id,
|
authority_id,
|
||||||
|
|||||||
78
src/server/BreCal/stubs/df_times.py
Normal file
78
src/server/BreCal/stubs/df_times.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import pandas as pd
|
||||||
|
import random
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from BreCal.stubs.times_agency import get_times_agency
|
||||||
|
from BreCal.stubs.times_bsmd import get_times_bsmd
|
||||||
|
from BreCal.stubs.times_mooring import get_times_mooring
|
||||||
|
from BreCal.stubs.times_pilot import get_times_pilot
|
||||||
|
from BreCal.stubs.times_portauthority import get_times_port_authority
|
||||||
|
from BreCal.stubs.times_terminal import get_times_terminal
|
||||||
|
from BreCal.stubs.times_tug import get_times_tug
|
||||||
|
from BreCal.database.enums import ParticipantType
|
||||||
|
|
||||||
|
def get_df_times(shipcall=None):
|
||||||
|
"""in case of providing a shipcall, one can read the id to set each times entry in the dataframe towards that shipcall id"""
|
||||||
|
df_times = pd.DataFrame([
|
||||||
|
fct()
|
||||||
|
|
||||||
|
for fct in [
|
||||||
|
get_times_agency,
|
||||||
|
get_times_bsmd,
|
||||||
|
get_times_mooring,
|
||||||
|
get_times_pilot,
|
||||||
|
get_times_port_authority,
|
||||||
|
get_times_terminal,
|
||||||
|
get_times_tug
|
||||||
|
]
|
||||||
|
])
|
||||||
|
if shipcall is not None:
|
||||||
|
df_times.loc[:,"shipcall_id"] = shipcall.id
|
||||||
|
return df_times
|
||||||
|
|
||||||
|
def random_time_perturbation(df_times, query):
|
||||||
|
# random perturbations
|
||||||
|
population = [datetime.datetime.now(), None, pd.NaT]
|
||||||
|
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, query] = random.sample(population,k=1)[0]
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.MOORING.value, query] = random.sample(population,k=1)[0]
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.PORT_ADMINISTRATION.value, query] = random.sample(population,k=1)[0]
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, query] = random.sample(population,k=1)[0]
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.TUG.value, query] = random.sample(population,k=1)[0]
|
||||||
|
return df_times
|
||||||
|
|
||||||
|
def get_df_times_participants_disagree(query, shipcall=None, df_times = None):
|
||||||
|
if df_times is None:
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
df_times = random_time_perturbation(df_times=df_times, query=query)
|
||||||
|
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, query] = datetime.datetime.now()+datetime.timedelta(hours=2, minutes=14)
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, query] = datetime.datetime.now()+datetime.timedelta(hours=1, minutes=7)
|
||||||
|
|
||||||
|
return df_times
|
||||||
|
|
||||||
|
def build_stub_df_times(shipcall, query, reference_time):
|
||||||
|
"""creates an artificial dataset, which simulates having too many shipcalls with too many identical times"""
|
||||||
|
df_times_a = get_df_times(shipcall)
|
||||||
|
df_times_a = df_times_a.loc[df_times_a["participant_type"]==ParticipantType.AGENCY.value]
|
||||||
|
df_times_a.loc[:,query] = reference_time + datetime.timedelta(seconds=17)
|
||||||
|
|
||||||
|
df_times_b = get_df_times(shipcall)
|
||||||
|
df_times_b = df_times_b.loc[df_times_b["participant_type"]==ParticipantType.AGENCY.value]
|
||||||
|
df_times_b.loc[:,query] = reference_time + datetime.timedelta(seconds=21)
|
||||||
|
|
||||||
|
df_times_c = get_df_times(shipcall)
|
||||||
|
df_times_c = df_times_c.loc[df_times_c["participant_type"]==ParticipantType.AGENCY.value]
|
||||||
|
df_times_c.loc[:,query] = reference_time + datetime.timedelta(seconds=26)
|
||||||
|
|
||||||
|
df_times_d = get_df_times(shipcall)
|
||||||
|
df_times_d = df_times_d.loc[df_times_d["participant_type"]==ParticipantType.AGENCY.value]
|
||||||
|
df_times_d.loc[:,query] = reference_time + datetime.timedelta(seconds=28)
|
||||||
|
|
||||||
|
df_times_e = get_df_times(shipcall)
|
||||||
|
df_times_e = df_times_e.loc[df_times_e["participant_type"]==ParticipantType.AGENCY.value]
|
||||||
|
df_times_e.loc[:,query] = reference_time + datetime.timedelta(seconds=29)
|
||||||
|
|
||||||
|
return pd.concat([df_times_a, df_times_b, df_times_c, df_times_d, df_times_e],axis=0)
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
from BreCal.stubs.times_full import get_times_full_simple
|
||||||
|
from BreCal.database.enums import ParticipantType
|
||||||
|
|
||||||
|
def get_times_agency():
|
||||||
|
times_agency = get_times_full_simple()
|
||||||
|
times_agency.participant_type = ParticipantType.AGENCY.value
|
||||||
|
return times_agency
|
||||||
|
|
||||||
8
src/server/BreCal/stubs/times_bsmd.py
Normal file
8
src/server/BreCal/stubs/times_bsmd.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from BreCal.stubs.times_full import get_times_full_simple
|
||||||
|
from BreCal.database.enums import ParticipantType
|
||||||
|
|
||||||
|
def get_times_bsmd():
|
||||||
|
times_bsmd = get_times_full_simple()
|
||||||
|
times_bsmd.participant_type = ParticipantType.BSMD.value
|
||||||
|
return times_bsmd
|
||||||
|
|
||||||
@ -3,9 +3,11 @@ this stub creates an example time object, where the times of every role are pres
|
|||||||
users will thereby be able to modify these values
|
users will thereby be able to modify these values
|
||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from BreCal.stubs import generate_uuid1_int
|
from BreCal.stubs import generate_uuid1_int
|
||||||
from BreCal.schemas.model import Times
|
from BreCal.schemas.model import Times
|
||||||
|
|
||||||
|
|
||||||
def get_times_full_simple():
|
def get_times_full_simple():
|
||||||
# only used for the stub
|
# only used for the stub
|
||||||
base_time = datetime.datetime.now()
|
base_time = datetime.datetime.now()
|
||||||
@ -65,3 +67,5 @@ def get_times_full_simple():
|
|||||||
modified=modified,
|
modified=modified,
|
||||||
)
|
)
|
||||||
return times
|
return times
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
from BreCal.stubs.times_full import get_times_full_simple
|
||||||
|
from BreCal.database.enums import ParticipantType
|
||||||
|
|
||||||
|
def get_times_mooring():
|
||||||
|
times_mooring = get_times_full_simple()
|
||||||
|
times_mooring.participant_type = ParticipantType.MOORING.value
|
||||||
|
return times_mooring
|
||||||
|
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
from BreCal.stubs.times_full import get_times_full_simple
|
||||||
|
from BreCal.database.enums import ParticipantType
|
||||||
|
|
||||||
|
def get_times_pilot():
|
||||||
|
times_pilot = get_times_full_simple()
|
||||||
|
times_pilot.participant_type = ParticipantType.PILOT.value
|
||||||
|
return times_pilot
|
||||||
|
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
from BreCal.stubs.times_full import get_times_full_simple
|
||||||
|
from BreCal.database.enums import ParticipantType
|
||||||
|
|
||||||
|
def get_times_port_authority():
|
||||||
|
times_port_authority = get_times_full_simple()
|
||||||
|
times_port_authority.participant_type = ParticipantType.PORT_ADMINISTRATION.value
|
||||||
|
return times_port_authority
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
from BreCal.stubs.times_full import get_times_full_simple
|
||||||
|
from BreCal.database.enums import ParticipantType
|
||||||
|
|
||||||
|
def get_times_terminal():
|
||||||
|
times_terminal = get_times_full_simple()
|
||||||
|
times_terminal.participant_type = ParticipantType.TERMINAL.value
|
||||||
|
return times_terminal
|
||||||
|
|
||||||
8
src/server/BreCal/stubs/times_tug.py
Normal file
8
src/server/BreCal/stubs/times_tug.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from BreCal.stubs.times_full import get_times_full_simple
|
||||||
|
from BreCal.database.enums import ParticipantType
|
||||||
|
|
||||||
|
def get_times_tug():
|
||||||
|
times_tug = get_times_full_simple()
|
||||||
|
times_tug.participant_type = ParticipantType.TUG.value
|
||||||
|
return times_tug
|
||||||
|
|
||||||
@ -73,17 +73,22 @@ class ValidationRuleBaseFunctions():
|
|||||||
when the query_time lays in the future, the delta is positive
|
when the query_time lays in the future, the delta is positive
|
||||||
|
|
||||||
returns a violation state depending on whether the delta is
|
returns a violation state depending on whether the delta is
|
||||||
Violation, if: 0 >= delta > threshold
|
Violation, if: 0 >= delta <= threshold
|
||||||
|
|
||||||
When the key time is defined (not None), there is no violation. Returns False
|
When the key time is defined (not None), there is no violation. Returns False
|
||||||
|
|
||||||
options:
|
options:
|
||||||
query_time: will be used to measure the time difference of 'now' until the query time
|
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
|
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
|
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 not applicable -> return 'GREEN'
|
||||||
if self.check_is_not_a_time_or_is_none(key_time) or self.check_is_not_a_time_or_is_none(query_time):
|
# 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
|
return False
|
||||||
|
|
||||||
# otherwise, this rule applies and the difference between 'now' and the query time is measured
|
# otherwise, this rule applies and the difference between 'now' and the query time is measured
|
||||||
@ -91,7 +96,7 @@ class ValidationRuleBaseFunctions():
|
|||||||
|
|
||||||
# a violation occurs, when the delta (in minutes) exceeds the specified threshold of a participant
|
# 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
|
# to prevent past-events from triggering violations, negative values are ignored
|
||||||
# Violation, if 0 >= delta >= threshold
|
# Violation, if 0 <= delta <= threshold
|
||||||
violation_state = (delta >= 0) and (delta<=threshold)
|
violation_state = (delta >= 0) and (delta<=threshold)
|
||||||
return violation_state
|
return violation_state
|
||||||
|
|
||||||
@ -141,7 +146,7 @@ class ValidationRuleBaseFunctions():
|
|||||||
violation_state = n_unique_times!=1
|
violation_state = n_unique_times!=1
|
||||||
return violation_state
|
return violation_state
|
||||||
|
|
||||||
def check_unique_shipcall_counts(self, query:str, rounding="min", maximum_threshold=3)->bool:
|
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
|
# base function for all validation rules in the group {0005} A&B
|
||||||
|
|
||||||
@ -150,10 +155,11 @@ class ValidationRuleBaseFunctions():
|
|||||||
"""
|
"""
|
||||||
# filter the df: keep only times_agents
|
# filter the df: keep only times_agents
|
||||||
# filter out all NaN and NaT entries
|
# filter out all NaN and NaT entries
|
||||||
times_agency = self.sql_handler.get_times_for_agency(non_null_column=query)
|
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
|
# get values and optionally round the values (internally)
|
||||||
(values, unique, counts) = self.sql_handler.get_unique_ship_counts(all_df_times=times_agency, query=query, rounding=rounding, maximum_threshold=maximum_threshold)
|
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
|
# when ANY of the unique values exceeds the threshold, a violation is observed
|
||||||
violation_state = np.any(np.greater(counts, maximum_threshold))
|
violation_state = np.any(np.greater(counts, maximum_threshold))
|
||||||
@ -651,7 +657,7 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
|
|||||||
return (StatusFlags.GREEN, None)
|
return (StatusFlags.GREEN, None)
|
||||||
|
|
||||||
# check, whether the start of operations is AFTER the estimated arrival time
|
# check, whether the start of operations is AFTER the estimated arrival time
|
||||||
violation_state = times_terminal.operations_start<times_agency.eta_berth
|
violation_state = times_terminal.operations_start < times_agency.eta_berth
|
||||||
|
|
||||||
if violation_state:
|
if violation_state:
|
||||||
validation_name = inspect.currentframe().f_code.co_name
|
validation_name = inspect.currentframe().f_code.co_name
|
||||||
@ -744,19 +750,20 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
|
|||||||
else:
|
else:
|
||||||
return (StatusFlags.GREEN, None)
|
return (StatusFlags.GREEN, None)
|
||||||
|
|
||||||
def validation_rule_fct_too_many_identical_eta_times(self, shipcall, df_times, rounding = "min", maximum_threshold = 3, *args, **kwargs):
|
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
|
Code: #0005-A
|
||||||
Type: Global Rule
|
Type: Global Rule
|
||||||
Description: this validation rule checks, whether there are too many shipcalls with identical ETA times.
|
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)
|
# check, if the header is filled in (agency)
|
||||||
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1:
|
if len(times_agency) != 1:
|
||||||
return (StatusFlags.GREEN, None)
|
return (StatusFlags.GREEN, None)
|
||||||
|
|
||||||
# when ANY of the unique values exceeds the threshold, a violation is observed
|
# when ANY of the unique values exceeds the threshold, a violation is observed
|
||||||
query = "eta_berth"
|
query = "eta_berth"
|
||||||
violation_state = self.check_unique_shipcall_counts(query, rounding=rounding, maximum_threshold=maximum_threshold)
|
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:
|
if violation_state:
|
||||||
validation_name = inspect.currentframe().f_code.co_name
|
validation_name = inspect.currentframe().f_code.co_name
|
||||||
@ -764,19 +771,20 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
|
|||||||
else:
|
else:
|
||||||
return (StatusFlags.GREEN, None)
|
return (StatusFlags.GREEN, None)
|
||||||
|
|
||||||
def validation_rule_fct_too_many_identical_etd_times(self, shipcall, df_times, rounding = "min", maximum_threshold = 3, *args, **kwargs):
|
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
|
Code: #0005-B
|
||||||
Type: Global Rule
|
Type: Global Rule
|
||||||
Description: this validation rule checks, whether there are too many shipcalls with identical ETD times.
|
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)
|
# check, if the header is filled in (agency)
|
||||||
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1:
|
if len(times_agency) != 1:
|
||||||
return (StatusFlags.GREEN, None)
|
return (StatusFlags.GREEN, None)
|
||||||
|
|
||||||
# when ANY of the unique values exceeds the threshold, a violation is observed
|
# when ANY of the unique values exceeds the threshold, a violation is observed
|
||||||
query = "etd_berth"
|
query = "etd_berth"
|
||||||
violation_state = self.check_unique_shipcall_counts(query, rounding=rounding, maximum_threshold=maximum_threshold)
|
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:
|
if violation_state:
|
||||||
validation_name = inspect.currentframe().f_code.co_name
|
validation_name = inspect.currentframe().f_code.co_name
|
||||||
|
|||||||
@ -44,7 +44,7 @@ def test_import_webargs():
|
|||||||
from webargs.flaskparser import parser
|
from webargs.flaskparser import parser
|
||||||
return
|
return
|
||||||
|
|
||||||
def test_import_mashmallow():
|
def test_import_marshmallow():
|
||||||
"""currently used in ~/brecal/src/server/BreCal/api/shipcalls.py"""
|
"""currently used in ~/brecal/src/server/BreCal/api/shipcalls.py"""
|
||||||
import marshmallow
|
import marshmallow
|
||||||
from marshmallow import Schema, fields
|
from marshmallow import Schema, fields
|
||||||
|
|||||||
@ -1,7 +1,13 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
import datetime
|
||||||
|
import pandas as pd
|
||||||
from BreCal.validators.validation_rule_functions import ValidationRuleFunctions
|
from BreCal.validators.validation_rule_functions import ValidationRuleFunctions
|
||||||
from BreCal.validators.validation_rules import ValidationRules
|
from BreCal.validators.validation_rules import ValidationRules
|
||||||
from BreCal.database.sql_handler import SQLHandler
|
from BreCal.database.sql_handler import SQLHandler
|
||||||
|
from BreCal.database.enums import ParticipantwiseTimeDelta, ParticipantType, StatusFlags, ShipcallType
|
||||||
|
|
||||||
|
from BreCal.stubs.shipcall import get_shipcall_simple
|
||||||
|
from BreCal.stubs.df_times import get_df_times, random_time_perturbation, get_df_times_participants_disagree, build_stub_df_times
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def build_sql_proxy_connection():
|
def build_sql_proxy_connection():
|
||||||
@ -25,38 +31,796 @@ def test_build_validation_rule_functions(build_sql_proxy_connection):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_time_delta_violation_query_time_to_now_key_time_is_defined(build_sql_proxy_connection):
|
||||||
def test_validation_rule_fct_agency_and_terminal_pier_side_disagreement(build_sql_proxy_connection):
|
|
||||||
"""#0006-A validation_rule_fct_agency_and_terminal_pier_side_disagreement"""
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
from BreCal.stubs.times_full import get_times_full_simple
|
|
||||||
from BreCal.stubs.shipcall import get_shipcall_simple
|
|
||||||
from BreCal.database.enums import ParticipantType
|
|
||||||
from BreCal.database.enums import StatusFlags
|
|
||||||
|
|
||||||
vr = build_sql_proxy_connection["vr"]
|
vr = build_sql_proxy_connection["vr"]
|
||||||
shipcall = get_shipcall_simple()
|
|
||||||
t1 = get_times_full_simple()
|
|
||||||
t2 = get_times_full_simple()
|
|
||||||
|
|
||||||
# roles: agency & terminal
|
# ship arrives in three hours, while the threshold for an alert is (e.g.) 5 hours
|
||||||
t1.participant_type = ParticipantType.AGENCY.value
|
# key time is given, so the function should always return False (no violation)
|
||||||
t2.participant_type = ParticipantType.TERMINAL.value
|
query_time = datetime.datetime.now() + datetime.timedelta(hours=3)
|
||||||
|
key_time = datetime.datetime.now() + datetime.timedelta(hours=7, minutes=30)
|
||||||
|
|
||||||
# disagreement
|
threshold = 60*5
|
||||||
t1.pier_side = True
|
violation_state = vr.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
|
||||||
t2.pier_side = False
|
assert not violation_state, f"the key time is filled in, so there should not be a violation"
|
||||||
|
|
||||||
time_objects = [t1, t2]
|
|
||||||
df_times = pd.DataFrame.from_records([to_.__dict__ for to_ in time_objects])
|
|
||||||
df_times.set_index('id',inplace=True)
|
|
||||||
|
|
||||||
(state, description) = vr.validation_rule_fct_agency_and_terminal_pier_side_disagreement(shipcall, df_times)
|
|
||||||
assert state.value > StatusFlags.GREEN.value, f"a violation must be identified"
|
|
||||||
assert description is not None, f"a violation description must be identified"
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def test_check_time_delta_violation_query_time_to_now_no_key_time_but_event_in_distant_future(build_sql_proxy_connection):
|
||||||
|
vr = build_sql_proxy_connection["vr"]
|
||||||
|
|
||||||
|
# ship arrives in three hours, while the threshold for an alert is (e.g.) 5 hours
|
||||||
|
# key time is given, so the function should always return False (no violation)
|
||||||
|
# query time (-> delta) & threshold have the same time -> no violation
|
||||||
|
query_time = datetime.datetime.now() + datetime.timedelta(hours=5, seconds=10) # when the delta & threshold are identical, microseconds between checking the time and defining it here, raise the violation
|
||||||
|
key_time = None
|
||||||
|
|
||||||
|
threshold = 60*5
|
||||||
|
violation_state = vr.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
|
||||||
|
assert not violation_state, f"the event is still far enough away, so there should not be a violation."
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_check_time_delta_violation_query_time_to_now_key_time_is_none(build_sql_proxy_connection):
|
||||||
|
vr = build_sql_proxy_connection["vr"]
|
||||||
|
|
||||||
|
# ship arrives in three hours, while the threshold for an alert is (e.g.) 5 hours
|
||||||
|
# key time is given, so the function should always return False (no violation)
|
||||||
|
query_time = datetime.datetime.now() + datetime.timedelta(hours=3)
|
||||||
|
key_time = None
|
||||||
|
|
||||||
|
threshold = 60*5 # minutes
|
||||||
|
violation_state = vr.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
|
||||||
|
assert violation_state, f"when the key time is not filled in and the query time is 'dangerously close', there should be a violation to indicate the traffic state"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_check_time_delta_violation_query_time_to_now_key_time_is_pd_nat(build_sql_proxy_connection):
|
||||||
|
vr = build_sql_proxy_connection["vr"]
|
||||||
|
|
||||||
|
# ship arrives in three hours, while the threshold for an alert is (e.g.) 5 hours
|
||||||
|
# key time is given, so the function should always return False (no violation)
|
||||||
|
query_time = datetime.datetime.now() + datetime.timedelta(hours=3)
|
||||||
|
key_time = pd.NaT
|
||||||
|
|
||||||
|
threshold = 60*5 # minutes
|
||||||
|
violation_state = vr.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
|
||||||
|
assert violation_state, f"when the key time is not filled in and the query time is 'dangerously close', there should be a violation to indicate the traffic state"
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_validation_rule_fct_missing_time_agency_berth_eta__missing_time_agency_no_violation(build_sql_proxy_connection):
|
||||||
|
"""0001-A validation_rule_fct_missing_time_agency_berth_eta"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
# artificially remove the agency, so the function is properly checked
|
||||||
|
df_times = df_times.loc[df_times["participant_type"]!=ParticipantType.AGENCY.value]
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_missing_time_agency_berth_eta(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.GREEN, f"function should return 'green', because the agency's entry is not present"
|
||||||
|
assert msg is None, f"with a 'green' state, there should be no message returned"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_missing_time_agency_berth_eta__shipcall_eta_dangerously_close_no_times_agency(build_sql_proxy_connection):
|
||||||
|
"""0001-A validation_rule_fct_missing_time_agency_berth_eta"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
# the shipcall happens 'soon'
|
||||||
|
shipcall.eta = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.AGENCY-10)
|
||||||
|
|
||||||
|
# set times agency to be undetermined
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = None
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_missing_time_agency_berth_eta(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the agency's entry is not present and the shipcall takes place soon"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_missing_time_agency_berth_eta__shipcall_eta_distant_enough_no_times_agency(build_sql_proxy_connection):
|
||||||
|
"""0001-A validation_rule_fct_missing_time_agency_berth_eta"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
# the shipcall happens 'soon'
|
||||||
|
shipcall.eta = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.AGENCY+10)
|
||||||
|
|
||||||
|
# set times agency to be undetermined
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = None
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_missing_time_agency_berth_eta(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.GREEN, f"function should return 'yellow', because the agency's entry is not present and the shipcall takes place soon"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_missing_time_agency_berth_eta__shipcall_eta_is_undefined_agency_eta_is_defined(build_sql_proxy_connection):
|
||||||
|
"""0001-A validation_rule_fct_missing_time_agency_berth_eta"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
# the shipcall is undefined
|
||||||
|
shipcall.eta = None
|
||||||
|
|
||||||
|
# set times agency to be undetermined
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = None
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_missing_time_agency_berth_eta(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.GREEN, f"function should return 'yellow', because the agency's entry is not present and the shipcall takes place soon"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_missing_time_agency_berth_etd__shipcall_etd_is_undefined_agency_etd_is_defined(build_sql_proxy_connection):
|
||||||
|
"""0001-B validation_rule_fct_missing_time_agency_berth_etd"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
# the shipcall etd is 'soon'
|
||||||
|
shipcall.etd = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.AGENCY-10)
|
||||||
|
|
||||||
|
# set times agency to be undetermined
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = None
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_missing_time_agency_berth_etd(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the agency's entry is not present and the shipcall takes place soon"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_missing_time_mooring_berth_eta__shipcall_soon_but_participant_estimated_time_undefined(build_sql_proxy_connection):
|
||||||
|
"""0001-C validation_rule_fct_missing_time_mooring_berth_eta"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
# according to the agency, a shipcall takes place soon (ETA/ETD)
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.MOORING-10)
|
||||||
|
|
||||||
|
# set times agency to be undetermined
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.MOORING.value, "eta_berth"] = None
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_missing_time_mooring_berth_eta(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_validation_rule_fct_missing_time_mooring_berth_etd__shipcall_soon_but_participant_estimated_time_undefined(build_sql_proxy_connection):
|
||||||
|
"""0001-D validation_rule_fct_missing_time_mooring_berth_etd"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
# according to the agency, a shipcall takes place soon (ETA/ETD)
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.MOORING-10)
|
||||||
|
|
||||||
|
# set times agency to be undetermined
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.MOORING.value, "etd_berth"] = None
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_missing_time_mooring_berth_etd(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_validation_rule_fct_missing_time_portadministration_berth_eta__shipcall_soon_but_participant_estimated_time_undefined(build_sql_proxy_connection):
|
||||||
|
"""0001-F validation_rule_fct_missing_time_portadministration_berth_eta"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
# according to the agency, a shipcall takes place soon (ETA/ETD)
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.PORT_ADMINISTRATION-10)
|
||||||
|
|
||||||
|
# set times agency to be undetermined
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.PORT_ADMINISTRATION.value, "eta_berth"] = None
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_missing_time_portadministration_berth_eta(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_validation_rule_fct_missing_time_portadministration_berth_etd__shipcall_soon_but_participant_estimated_time_undefined(build_sql_proxy_connection):
|
||||||
|
"""0001-G validation_rule_fct_missing_time_portadministration_berth_etd"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
# according to the agency, a shipcall takes place soon (ETA/ETD)
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.PORT_ADMINISTRATION-10)
|
||||||
|
|
||||||
|
# set times agency to be undetermined
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.PORT_ADMINISTRATION.value, "etd_berth"] = None
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_missing_time_portadministration_berth_etd(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_missing_time_pilot_berth_eta__shipcall_soon_but_participant_estimated_time_undefined(build_sql_proxy_connection):
|
||||||
|
"""0001-H validation_rule_fct_missing_time_pilot_berth_eta"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
# according to the agency, a shipcall takes place soon (ETA/ETD)
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.PILOT-10)
|
||||||
|
|
||||||
|
# set times agency to be undetermined
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, "eta_berth"] = None
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_missing_time_pilot_berth_eta(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_missing_time_pilot_berth_etd__shipcall_soon_but_participant_estimated_time_undefined(build_sql_proxy_connection):
|
||||||
|
"""0001-I validation_rule_fct_missing_time_pilot_berth_etd"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
# according to the agency, a shipcall takes place soon (ETA/ETD)
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.PILOT-10)
|
||||||
|
|
||||||
|
# set times agency to be undetermined
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, "etd_berth"] = None
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_missing_time_pilot_berth_etd(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_validation_rule_fct_missing_time_tug_berth_eta__shipcall_soon_but_participant_estimated_time_undefined(build_sql_proxy_connection):
|
||||||
|
"""0001-J validation_rule_fct_missing_time_tug_berth_eta"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
# according to the agency, a shipcall takes place soon (ETA/ETD)
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.TUG-10)
|
||||||
|
|
||||||
|
# set times agency to be undetermined
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.TUG.value, "eta_berth"] = None
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_missing_time_tug_berth_eta(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_validation_rule_fct_missing_time_tug_berth_etd__shipcall_soon_but_participant_estimated_time_undefined(build_sql_proxy_connection):
|
||||||
|
"""0001-K validation_rule_fct_missing_time_tug_berth_etd"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
# according to the agency, a shipcall takes place soon (ETA/ETD)
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.TUG-10)
|
||||||
|
|
||||||
|
# set times agency to be undetermined
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.TUG.value, "etd_berth"] = None
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_missing_time_tug_berth_etd(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_validation_rule_fct_missing_time_terminal_berth_eta__shipcall_soon_but_participant_estimated_time_undefined(build_sql_proxy_connection):
|
||||||
|
"""0001-L validation_rule_fct_missing_time_terminal_berth_eta"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
# according to the agency, a shipcall takes place soon (ETA/ETD)
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.TERMINAL-10)
|
||||||
|
|
||||||
|
# set times agency to be undetermined
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "eta_berth"] = None
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_missing_time_terminal_berth_eta(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_validation_rule_fct_missing_time_terminal_berth_etd__shipcall_soon_but_participant_estimated_time_undefined(build_sql_proxy_connection):
|
||||||
|
"""0001-M validation_rule_fct_missing_time_terminal_berth_etd"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
# according to the agency, a shipcall takes place soon (ETA/ETD)
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.TERMINAL-10)
|
||||||
|
|
||||||
|
# set times agency to be undetermined
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "etd_berth"] = None
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_missing_time_terminal_berth_etd(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def test_validation_rule_fct_shipcall_incoming_participants_disagree_on_eta__participants_disagree_on_time_but_different_shipcall_type(build_sql_proxy_connection):
|
||||||
|
"""0002-A validation_rule_fct_shipcall_incoming_participants_disagree_on_eta"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
|
||||||
|
# set shipcall type to NOT match the function -> returns 'green'
|
||||||
|
query = "eta_berth"
|
||||||
|
shipcall.type = ShipcallType.SHIFTING.value
|
||||||
|
df_times = get_df_times_participants_disagree(query=query, shipcall=shipcall)
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_shipcall_incoming_participants_disagree_on_eta(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.GREEN, f"function should return 'green', because the shipcall type does not match the function"
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def test_validation_rule_fct_shipcall_incoming_participants_disagree_on_eta__participants_disagree_on_time(build_sql_proxy_connection):
|
||||||
|
"""0002-A validation_rule_fct_shipcall_incoming_participants_disagree_on_eta"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
|
||||||
|
# set shipcall type to match the function
|
||||||
|
query = "eta_berth"
|
||||||
|
shipcall.type = ShipcallType.INCOMING.value
|
||||||
|
df_times = get_df_times_participants_disagree(query=query, shipcall=shipcall) # makes sure that there is disagreement among participants
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_shipcall_incoming_participants_disagree_on_eta(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.RED, f"function should return 'red', because agency and pilot disagree on the query"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_shipcall_incoming_participants_disagree_on_eta__participants_agree_on_time(build_sql_proxy_connection):
|
||||||
|
"""0002-A validation_rule_fct_shipcall_incoming_participants_disagree_on_eta"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
|
||||||
|
# set shipcall type to NOT match the function -> returns 'green'
|
||||||
|
query = "eta_berth"
|
||||||
|
shipcall.type = ShipcallType.INCOMING.value
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
df_times.loc[:,query] = datetime.datetime.now()
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_shipcall_incoming_participants_disagree_on_eta(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.GREEN, f"function should return 'green', because the participants fully agree on the time"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd__participants_disagree_on_time_but_different_shipcall_type(build_sql_proxy_connection):
|
||||||
|
"""0002-B validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
|
||||||
|
# set shipcall type to NOT match the function -> returns 'green'
|
||||||
|
query = "etd_berth"
|
||||||
|
shipcall.type = ShipcallType.SHIFTING.value
|
||||||
|
df_times = get_df_times_participants_disagree(query=query, shipcall=shipcall)
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.GREEN, f"function should return 'green', because the shipcall type does not match the function"
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def test_validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd__participants_disagree_on_time(build_sql_proxy_connection):
|
||||||
|
"""0002-B validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
|
||||||
|
# set shipcall type to match the function
|
||||||
|
query = "etd_berth"
|
||||||
|
shipcall.type = ShipcallType.OUTGOING.value
|
||||||
|
df_times = get_df_times_participants_disagree(query=query, shipcall=shipcall) # makes sure that there is disagreement among participants
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.RED, f"function should return 'red', because agency and pilot disagree on the query"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd__participants_agree_on_time(build_sql_proxy_connection):
|
||||||
|
"""0002-B validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
|
||||||
|
# set shipcall type to NOT match the function -> returns 'green'
|
||||||
|
query = "etd_berth"
|
||||||
|
shipcall.type = ShipcallType.OUTGOING.value
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
df_times.loc[:,query] = datetime.datetime.now()
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.GREEN, f"function should return 'green', because the participants fully agree on the time"
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd__participants_disagree_on_time_but_different_shipcall_type(build_sql_proxy_connection):
|
||||||
|
"""0002-C validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
shipcall.type = ShipcallType.INCOMING.value
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
# set shipcall type to match the function
|
||||||
|
query = "eta_berth"
|
||||||
|
df_times = get_df_times_participants_disagree(query=query, shipcall=shipcall, df_times=df_times) # makes sure that there is disagreement among participants
|
||||||
|
|
||||||
|
# set shipcall type to match the function
|
||||||
|
query = "etd_berth"
|
||||||
|
df_times = get_df_times_participants_disagree(query=query, shipcall=shipcall, df_times=df_times) # makes sure that there is disagreement among participants
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.GREEN, f"function should return 'green', because the shipcall type does not match the function"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd__participants_disagree_on_time(build_sql_proxy_connection):
|
||||||
|
"""0002-C validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
shipcall.type = ShipcallType.SHIFTING.value
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
# set shipcall type to match the function
|
||||||
|
query = "eta_berth"
|
||||||
|
df_times = get_df_times_participants_disagree(query=query, shipcall=shipcall, df_times=df_times) # makes sure that there is disagreement among participants
|
||||||
|
|
||||||
|
# set shipcall type to match the function
|
||||||
|
query = "etd_berth"
|
||||||
|
df_times = get_df_times_participants_disagree(query=query, shipcall=shipcall, df_times=df_times) # makes sure that there is disagreement among participants
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.RED, f"function should return 'red', because agency and pilot disagree on the query"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd__participants_agree_on_time(build_sql_proxy_connection):
|
||||||
|
"""0002-C validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
shipcall.type = ShipcallType.SHIFTING.value
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
# set shipcall type to match the function
|
||||||
|
query = "eta_berth"
|
||||||
|
df_times.loc[:,query] = datetime.datetime.now()
|
||||||
|
|
||||||
|
# set shipcall type to match the function
|
||||||
|
query = "etd_berth"
|
||||||
|
df_times.loc[:,query] = datetime.datetime.now()
|
||||||
|
|
||||||
|
# apply the validation rule
|
||||||
|
(state, msg) = vr.validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd(shipcall=shipcall, df_times=df_times)
|
||||||
|
|
||||||
|
# expectation: green state, no msg
|
||||||
|
assert state==StatusFlags.GREEN, f"function should return 'green', because the participants fully agree on the time"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_eta_time_not_in_operation_window__times_dont_match(build_sql_proxy_connection):
|
||||||
|
"""0003-A validation_rule_fct_eta_time_not_in_operation_window"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
t0_time = datetime.datetime.now() # reference time for easier readability
|
||||||
|
|
||||||
|
# the planned operations_start is before eta_berth (by one minute in this case)
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = t0_time + datetime.timedelta(minutes=1)
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "operations_start"] = t0_time + datetime.timedelta(minutes=0)
|
||||||
|
|
||||||
|
(code, msg) = vr.validation_rule_fct_eta_time_not_in_operation_window(shipcall, df_times)
|
||||||
|
assert code==StatusFlags.RED, f"status flag should be 'red', because the planned operations start is BEFORE the estimated time of arrival for the shipcall"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_etd_time_not_in_operation_window__times_dont_match(build_sql_proxy_connection):
|
||||||
|
"""0003-B validation_rule_fct_etd_time_not_in_operation_window"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
t0_time = datetime.datetime.now() # reference time for easier readability
|
||||||
|
|
||||||
|
# the planned operations_end is after etd_berth (by one minute in this case)
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = t0_time + datetime.timedelta(hours=1)
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "operations_end"] = t0_time+datetime.timedelta(hours=1, minutes=1)
|
||||||
|
|
||||||
|
|
||||||
|
(code, msg) = vr.validation_rule_fct_etd_time_not_in_operation_window(shipcall, df_times)
|
||||||
|
assert code==StatusFlags.RED, f"status flag should be 'red', because the planned operations end is AFTER the estimated time of departure for the shipcall"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_eta_time_not_in_operation_window_and_validation_rule_fct_etd_time_not_in_operation_window__always_okay(build_sql_proxy_connection):
|
||||||
|
"""
|
||||||
|
0003-A validation_rule_fct_eta_time_not_in_operation_window
|
||||||
|
0003-B validation_rule_fct_etd_time_not_in_operation_window
|
||||||
|
"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
import random
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
t0_time = datetime.datetime.now()
|
||||||
|
|
||||||
|
# 10 random permutations of None/pd.NaT/suitable values
|
||||||
|
# each of these combinations is okay and should return a 'green' state
|
||||||
|
for _i in range(10):
|
||||||
|
# eta_berth & operations start
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = random.sample([None, pd.NaT, t0_time + datetime.timedelta(minutes=0)],k=1)[0]
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "operations_start"] = random.sample([None, pd.NaT, t0_time + datetime.timedelta(minutes=0)], k=1)[0]
|
||||||
|
|
||||||
|
# etd_berth & operations start
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = random.sample([None, pd.NaT, t0_time + datetime.timedelta(hours=1)],k=1)[0]
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "operations_end"] = random.sample([None, pd.NaT, t0_time+datetime.timedelta(hours=1)],k=1)[0]
|
||||||
|
|
||||||
|
(code, msg) = vr.validation_rule_fct_eta_time_not_in_operation_window(shipcall=shipcall, df_times=df_times)
|
||||||
|
assert code==StatusFlags.GREEN, f"status flag should be 'green', as any of these perturbations sets operation & estimated time to be on par ot one the values missed"
|
||||||
|
(code, msg) = vr.validation_rule_fct_etd_time_not_in_operation_window(shipcall=shipcall, df_times=df_times)
|
||||||
|
assert code==StatusFlags.GREEN, f"status flag should be 'green', as any of these perturbations sets operation & estimated time to be on par ot one the values missed"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_eta_time_not_in_tidal_window__is_okay(build_sql_proxy_connection):
|
||||||
|
"""0004-A validation_rule_fct_eta_time_not_in_tidal_window"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
t0_time = datetime.datetime.now()
|
||||||
|
|
||||||
|
# tidal window: [t0 +1min, t0 +1hr)
|
||||||
|
# eta berth:
|
||||||
|
shipcall.tidal_window_from = t0_time + datetime.timedelta(minutes=1)
|
||||||
|
shipcall.tidal_window_to = t0_time + datetime.timedelta(hours=1)
|
||||||
|
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = t0_time + datetime.timedelta(minutes=1)
|
||||||
|
(code, msg) = vr.validation_rule_fct_eta_time_not_in_tidal_window(shipcall, df_times)
|
||||||
|
assert code==StatusFlags.GREEN, f"state should be 'green', because eta_berth matches precisely the tidal_window_from"
|
||||||
|
|
||||||
|
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = t0_time + datetime.timedelta(hours=1)
|
||||||
|
(code, msg) = vr.validation_rule_fct_eta_time_not_in_tidal_window(shipcall, df_times)
|
||||||
|
assert code==StatusFlags.GREEN, f"state should be 'green', because eta_berth matches precisely the tidal_window_to (in this case, the etd_berth would likely cause an issue)"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_eta_time_not_in_tidal_window__eta_outside_tidal_window(build_sql_proxy_connection):
|
||||||
|
"""0004-A validation_rule_fct_eta_time_not_in_tidal_window"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
t0_time = datetime.datetime.now()
|
||||||
|
|
||||||
|
# tidal window: [t0 +1min, t0 +1hr)
|
||||||
|
# eta berth: t0+0min
|
||||||
|
shipcall.tidal_window_from = t0_time + datetime.timedelta(minutes=1)
|
||||||
|
shipcall.tidal_window_to = t0_time + datetime.timedelta(hours=1)
|
||||||
|
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = t0_time + datetime.timedelta(minutes=0)
|
||||||
|
(code, msg) = vr.validation_rule_fct_eta_time_not_in_tidal_window(shipcall, df_times)
|
||||||
|
assert code==StatusFlags.RED, f"state should be 'red', eta_berth takes place before the tidal window"
|
||||||
|
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = t0_time + datetime.timedelta(hours=1, minutes=1)
|
||||||
|
(code, msg) = vr.validation_rule_fct_eta_time_not_in_tidal_window(shipcall, df_times)
|
||||||
|
assert code==StatusFlags.RED, f"state should be 'red', eta_berth takes place after the tidal window"
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_validation_rule_fct_etd_time_not_in_tidal_window__is_okay(build_sql_proxy_connection):
|
||||||
|
"""0004-B validation_rule_fct_etd_time_not_in_tidal_window"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
t0_time = datetime.datetime.now()
|
||||||
|
|
||||||
|
# tidal window: [t0 +1min, t0 +1hr)
|
||||||
|
# etd berth:
|
||||||
|
shipcall.tidal_window_from = t0_time + datetime.timedelta(minutes=1)
|
||||||
|
shipcall.tidal_window_to = t0_time + datetime.timedelta(hours=1)
|
||||||
|
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = t0_time + datetime.timedelta(minutes=1)
|
||||||
|
(code, msg) = vr.validation_rule_fct_etd_time_not_in_tidal_window(shipcall, df_times)
|
||||||
|
assert code==StatusFlags.GREEN, f"state should be 'green', because etd_berth matches precisely the tidal_window_from"
|
||||||
|
|
||||||
|
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = t0_time + datetime.timedelta(hours=1)
|
||||||
|
(code, msg) = vr.validation_rule_fct_etd_time_not_in_tidal_window(shipcall, df_times)
|
||||||
|
assert code==StatusFlags.GREEN, f"state should be 'green', because etd_berth matches precisely the tidal_window_to (in this case, the etd_berth would likely cause an issue)"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_etd_time_not_in_tidal_window__etd_outside_tidal_window(build_sql_proxy_connection):
|
||||||
|
"""0004-B validation_rule_fct_etd_time_not_in_tidal_window"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
|
||||||
|
t0_time = datetime.datetime.now()
|
||||||
|
|
||||||
|
# tidal window: [t0 +1min, t0 +1hr)
|
||||||
|
# etd berth:
|
||||||
|
shipcall.tidal_window_from = t0_time + datetime.timedelta(minutes=1)
|
||||||
|
shipcall.tidal_window_to = t0_time + datetime.timedelta(hours=1)
|
||||||
|
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = t0_time + datetime.timedelta(minutes=0)
|
||||||
|
(code, msg) = vr.validation_rule_fct_etd_time_not_in_tidal_window(shipcall, df_times)
|
||||||
|
assert code==StatusFlags.RED, f"state should be 'red', etd_berth takes place before the tidal window"
|
||||||
|
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = t0_time + datetime.timedelta(hours=1, minutes=1)
|
||||||
|
(code, msg) = vr.validation_rule_fct_etd_time_not_in_tidal_window(shipcall, df_times)
|
||||||
|
assert code==StatusFlags.RED, f"state should be 'red', etd_berth takes place after the tidal window"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_too_many_identical_eta_times__is_violated_by_too_many_identical_times(build_sql_proxy_connection):
|
||||||
|
"""0005-A validation_rule_fct_too_many_identical_eta_times"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
query = "eta_berth"
|
||||||
|
|
||||||
|
reference_time = pd.Timestamp(datetime.datetime.now())
|
||||||
|
reference_time = reference_time.round("min")
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
df_times = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]
|
||||||
|
df_times.loc[:,query] = reference_time + datetime.timedelta(seconds=12)
|
||||||
|
|
||||||
|
all_times_df = build_stub_df_times(shipcall, query, reference_time)
|
||||||
|
(code, msg) = vr.validation_rule_fct_too_many_identical_eta_times(shipcall=shipcall, df_times=df_times, all_times_agency=all_times_df)
|
||||||
|
assert code == StatusFlags.YELLOW, f"status should be 'yellow', because the artificial 'all_times_df' contains five shipcalls with identical times, which exceeds the threshold"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_too_many_identical_etd_times__is_violated_by_too_many_identical_times(build_sql_proxy_connection):
|
||||||
|
"""0005-B validation_rule_fct_too_many_identical_etd_times"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
query = "etd_berth"
|
||||||
|
|
||||||
|
reference_time = pd.Timestamp(datetime.datetime.now())
|
||||||
|
reference_time = reference_time.round("min")
|
||||||
|
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
df_times = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]
|
||||||
|
df_times.loc[:,query] = reference_time + datetime.timedelta(seconds=12)
|
||||||
|
|
||||||
|
all_times_df = build_stub_df_times(shipcall, query, reference_time)
|
||||||
|
(code, msg) = vr.validation_rule_fct_too_many_identical_etd_times(shipcall=shipcall, df_times=df_times, all_times_agency=all_times_df)
|
||||||
|
assert code == StatusFlags.YELLOW, f"status should be 'yellow', because the artificial 'all_times_df' contains five shipcalls with identical times, which exceeds the threshold"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_agency_and_terminal_berth_id_disagreement__agency_and_terminal_agree(build_sql_proxy_connection):
|
||||||
|
"""0006-A validation_rule_fct_agency_and_terminal_berth_id_disagreement"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "berth_id"] = 143
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "berth_id"] = 143
|
||||||
|
|
||||||
|
(code, msg) = vr.validation_rule_fct_agency_and_terminal_berth_id_disagreement(shipcall=shipcall, df_times=df_times)
|
||||||
|
assert code==StatusFlags.GREEN, f"status should be 'green', because agency and terminal agree on the selected berth id"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_agency_and_terminal_berth_id_disagreement__agency_and_terminal_disagree(build_sql_proxy_connection):
|
||||||
|
"""0006-A validation_rule_fct_agency_and_terminal_berth_id_disagreement"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "berth_id"] = 143
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "berth_id"] = 145
|
||||||
|
|
||||||
|
(code, msg) = vr.validation_rule_fct_agency_and_terminal_berth_id_disagreement(shipcall=shipcall, df_times=df_times)
|
||||||
|
assert code==StatusFlags.YELLOW, f"status should be 'yellow', because agency and terminal do not agree on the selected berth id"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_agency_and_terminal_pier_side_disagreement__agency_and_terminal_agree(build_sql_proxy_connection):
|
||||||
|
"""0006-B validation_rule_fct_agency_and_terminal_pier_side_disagreement"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "pier_side"] = True
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "pier_side"] = True
|
||||||
|
|
||||||
|
(code, msg) = vr.validation_rule_fct_agency_and_terminal_pier_side_disagreement(shipcall=shipcall, df_times=df_times)
|
||||||
|
assert code==StatusFlags.GREEN, f"status should be 'green', because agency and terminal agree on the selected pier side"
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_agency_and_terminal_pier_side_disagreement__agency_and_terminal_disagree(build_sql_proxy_connection):
|
||||||
|
"""0006-B validation_rule_fct_agency_and_terminal_pier_side_disagreement"""
|
||||||
|
vr = build_sql_proxy_connection['vr']
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
df_times = get_df_times(shipcall)
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "pier_side"] = True
|
||||||
|
df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "pier_side"] = False
|
||||||
|
|
||||||
|
(code, msg) = vr.validation_rule_fct_agency_and_terminal_pier_side_disagreement(shipcall=shipcall, df_times=df_times)
|
||||||
|
assert code==StatusFlags.YELLOW, f"status should be 'yellow', because agency and terminal do not agree on the selected pier side"
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_validation_rule_fct_agency_and_terminal_pier_side_agreement(build_sql_proxy_connection):
|
def test_validation_rule_fct_agency_and_terminal_pier_side_agreement(build_sql_proxy_connection):
|
||||||
"""#0006-A validation_rule_fct_agency_and_terminal_pier_side_disagreement"""
|
"""#0006-A validation_rule_fct_agency_and_terminal_pier_side_disagreement"""
|
||||||
@ -89,8 +853,36 @@ def test_validation_rule_fct_agency_and_terminal_pier_side_agreement(build_sql_p
|
|||||||
assert description is None, f"no violation should be observed"
|
assert description is None, f"no violation should be observed"
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def test_validation_rule_fct_agency_and_terminal_pier_side_disagreement(build_sql_proxy_connection):
|
||||||
|
"""#0006-A validation_rule_fct_agency_and_terminal_pier_side_disagreement"""
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from BreCal.stubs.times_full import get_times_full_simple
|
||||||
|
from BreCal.stubs.shipcall import get_shipcall_simple
|
||||||
|
from BreCal.database.enums import ParticipantType
|
||||||
|
from BreCal.database.enums import StatusFlags
|
||||||
|
|
||||||
|
vr = build_sql_proxy_connection["vr"]
|
||||||
|
shipcall = get_shipcall_simple()
|
||||||
|
t1 = get_times_full_simple()
|
||||||
|
t2 = get_times_full_simple()
|
||||||
|
|
||||||
|
# roles: agency & terminal
|
||||||
|
t1.participant_type = ParticipantType.AGENCY.value
|
||||||
|
t2.participant_type = ParticipantType.TERMINAL.value
|
||||||
|
|
||||||
|
# disagreement
|
||||||
|
t1.pier_side = True
|
||||||
|
t2.pier_side = False
|
||||||
|
|
||||||
|
time_objects = [t1, t2]
|
||||||
|
df_times = pd.DataFrame.from_records([to_.__dict__ for to_ in time_objects])
|
||||||
|
df_times.set_index('id',inplace=True)
|
||||||
|
|
||||||
|
(state, description) = vr.validation_rule_fct_agency_and_terminal_pier_side_disagreement(shipcall, df_times)
|
||||||
|
assert state.value > StatusFlags.GREEN.value, f"a violation must be identified"
|
||||||
|
assert description is not None, f"a violation description must be identified"
|
||||||
|
return
|
||||||
|
|
||||||
def test_validation_rule_fct_agency_and_terminal_berth_id_disagreement(build_sql_proxy_connection):
|
def test_validation_rule_fct_agency_and_terminal_berth_id_disagreement(build_sql_proxy_connection):
|
||||||
"""#0006-B validation_rule_fct_agency_and_terminal_pier_side_disagreement"""
|
"""#0006-B validation_rule_fct_agency_and_terminal_pier_side_disagreement"""
|
||||||
|
|||||||
Reference in New Issue
Block a user