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:
scopesorting 2023-11-03 19:45:55 +01:00
parent b75ea6891c
commit 953144d011
16 changed files with 1033 additions and 64 deletions

View File

@ -13,6 +13,25 @@ from .api import ships
from .api import login
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):
app = Flask(__name__, instance_relative_config=True)
@ -47,22 +66,24 @@ def create_app(test_config=None):
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__ = [
"get_project_root",
"ensure_path",
"execute_test_with_pytest",
"execute_coverage_test",
"get_project_root",
"ensure_path",
"execute_test_with_pytest",
"execute_coverage_test",
"difference_to_then",
"TimeLogic",
"ValidationRules",
"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",
]

View File

@ -183,6 +183,9 @@ class SQLHandler():
# filter out all NaN and NaT entries
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
# filter by the agency participant_type
@ -192,13 +195,20 @@ class SQLHandler():
def filter_df_by_key_value(self, df, key, value)->pd.DataFrame:
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)"""
# get values and optional: rounding
values = all_df_times.loc[:, query]
# optional: rounding
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)
violation_state = np.any(np.greater(counts, maximum_threshold))
return (values, unique, counts)
# after rounding, filter {all_df_times}, so only those, which match the current query are of interest
# takes 'times_agency' to sample, which value should match
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)

View File

@ -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"""
from uuid import uuid1
return uuid1().int>>64

View File

@ -7,7 +7,6 @@ def get_berth_simple():
# Note: #TODO: name, participant_id & lock state are arbitrary
name = "Avangard Dalben"
participant_id = 1# e.g., Avangard
lock = False
owner_id = 1 # e.g., Avangard
authority_id = 1 # e.g., Avangard
@ -19,7 +18,6 @@ def get_berth_simple():
berth = Berth(
berth_id,
name,
participant_id,
lock,
owner_id,
authority_id,

View 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)

View File

@ -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

View 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

View File

@ -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
"""
import datetime
from BreCal.stubs import generate_uuid1_int
from BreCal.schemas.model import Times
def get_times_full_simple():
# only used for the stub
base_time = datetime.datetime.now()
@ -65,3 +67,5 @@ def get_times_full_simple():
modified=modified,
)
return times

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View 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

View File

@ -73,17 +73,22 @@ class ValidationRuleBaseFunctions():
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
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
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'
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
# 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
# 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)
return violation_state
@ -141,7 +146,7 @@ class ValidationRuleBaseFunctions():
violation_state = n_unique_times!=1
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
@ -150,10 +155,11 @@ class ValidationRuleBaseFunctions():
"""
# filter the df: keep only times_agents
# filter out all NaN and NaT entries
times_agency = self.sql_handler.get_times_for_agency(non_null_column=query)
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
(values, unique, counts) = self.sql_handler.get_unique_ship_counts(all_df_times=times_agency, query=query, rounding=rounding, maximum_threshold=maximum_threshold)
# 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))
@ -651,7 +657,7 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
return (StatusFlags.GREEN, None)
# check, whether the start of operations is AFTER the estimated arrival time
violation_state = times_terminal.operations_start<times_agency.eta_berth
violation_state = times_terminal.operations_start < times_agency.eta_berth
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
@ -744,19 +750,20 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_too_many_identical_eta_times(self, shipcall, df_times, rounding = "min", maximum_threshold = 3, *args, **kwargs):
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(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1:
if len(times_agency) != 1:
return (StatusFlags.GREEN, None)
# when ANY of the unique values exceeds the threshold, a violation is observed
query = "eta_berth"
violation_state = self.check_unique_shipcall_counts(query, rounding=rounding, maximum_threshold=maximum_threshold)
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 = inspect.currentframe().f_code.co_name
@ -764,19 +771,20 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_too_many_identical_etd_times(self, shipcall, df_times, rounding = "min", maximum_threshold = 3, *args, **kwargs):
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(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1:
if len(times_agency) != 1:
return (StatusFlags.GREEN, None)
# when ANY of the unique values exceeds the threshold, a violation is observed
query = "etd_berth"
violation_state = self.check_unique_shipcall_counts(query, rounding=rounding, maximum_threshold=maximum_threshold)
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 = inspect.currentframe().f_code.co_name

View File

@ -44,7 +44,7 @@ def test_import_webargs():
from webargs.flaskparser import parser
return
def test_import_mashmallow():
def test_import_marshmallow():
"""currently used in ~/brecal/src/server/BreCal/api/shipcalls.py"""
import marshmallow
from marshmallow import Schema, fields

View File

@ -1,7 +1,13 @@
import pytest
import datetime
import pandas as pd
from BreCal.validators.validation_rule_functions import ValidationRuleFunctions
from BreCal.validators.validation_rules import ValidationRules
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")
def build_sql_proxy_connection():
@ -25,38 +31,796 @@ def test_build_validation_rule_functions(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
def test_check_time_delta_violation_query_time_to_now_key_time_is_defined(build_sql_proxy_connection):
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
# 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 = datetime.datetime.now() + datetime.timedelta(hours=7, minutes=30)
# 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"
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 key time is filled in, so there should not be a violation"
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):
"""#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"
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):
"""#0006-B validation_rule_fct_agency_and_terminal_pier_side_disagreement"""