diff --git a/.gitignore b/.gitignore index 83afcd3..005dd7b 100644 --- a/.gitignore +++ b/.gitignore @@ -126,3 +126,165 @@ __pycache__/ # Local History for Visual Studio Code .history/ + + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file diff --git a/src/BreCalClient/MainWindow.xaml.cs b/src/BreCalClient/MainWindow.xaml.cs index 3402794..9389a70 100644 --- a/src/BreCalClient/MainWindow.xaml.cs +++ b/src/BreCalClient/MainWindow.xaml.cs @@ -189,8 +189,15 @@ namespace BreCalClient esc.ShipcallModel.Shipcall?.Participants.Clear(); foreach (ParticipantAssignment pa in esc.ShipcallModel.AssignedParticipants.Values) esc.ShipcallModel.Shipcall?.Participants.Add(pa); - this._api.ShipcallsPost(esc.ShipcallModel.Shipcall); // save new ship call - this.AddShipcall(esc.ShipcallModel); + try + { + this._api.ShipcallsPost(esc.ShipcallModel.Shipcall); // save new ship call + this.AddShipcall(esc.ShipcallModel); + } + catch(Exception ex) + { + this.ShowErrorDialog(ex.ToString(), ex.Message); + } _refreshImmediately = true; // set flag to avoid timer loop termination _tokenSource.Cancel(); // force timer loop end diff --git a/src/server/BreCal/database/enums.py b/src/server/BreCal/database/enums.py index e038643..3092fd8 100644 --- a/src/server/BreCal/database/enums.py +++ b/src/server/BreCal/database/enums.py @@ -1,6 +1,6 @@ -from enum import Enum +from enum import Enum, IntFlag -class ParticipantType(Enum): +class ParticipantType(IntFlag): """determines the type of a participant""" NONE = 0 BSMD = 1 diff --git a/src/server/BreCal/database/sql_handler.py b/src/server/BreCal/database/sql_handler.py index f34f345..59497e3 100644 --- a/src/server/BreCal/database/sql_handler.py +++ b/src/server/BreCal/database/sql_handler.py @@ -1,12 +1,26 @@ import numpy as np import pandas as pd import datetime +import typing from BreCal.schemas.model import Shipcall, Ship, Participant, Berth, User, Times from BreCal.database.enums import ParticipantType def pandas_series_to_data_model(): return +def set_participant_type(x, participant_df)->int: + """ + when iterating over each row entry x in the shipcall_participant_map, + one can update the 'type' column by extracting the matching data from a participant dataframe + + returns: participant_type + """ + participant_id = x["participant_id"] + participant_type = participant_df.loc[participant_id, "type"] + return participant_type + + + class SQLHandler(): """ An object that reads SQL queries from the sql_connection and stores it in pandas DataFrames. The object can read all available tables @@ -95,6 +109,10 @@ class SQLHandler(): # update the 'participants' column in 'shipcall' self.initialize_shipcall_participant_list() + + # update the 'type' in shipcall_participants_map + # fully deprecated + # self.add_participant_type_to_map() return def build_full_mysql_df_dict(self, all_schemas): @@ -121,6 +139,52 @@ class SQLHandler(): lambda x: self.get_participants(x.name), axis=1) return + + def add_participant_type_to_map(self): + """ + applies a lambda function, where the 'type'-column in the shipcall_participant_map is updated by reading the + respective data from the participants. Updates the shipcall_participant_map inplace. + """ + raise Exception("deprecated! Overwriting the shipcall_participant_map may cause harm, as a participant with multi-flag might be wrongfully assigned to multiple roles simultaneously.") + #spm = self.df_dict["shipcall_participant_map"] + #participant_df = self.df_dict["participant"] + + #spm.loc[:,"type"] = spm.loc[:].apply(lambda x: set_participant_type(x, participant_df=participant_df),axis=1) + #self.df_dict["shipcall_participant_map"] = spm + return + + def get_assigned_participants(self, shipcall)->pd.DataFrame: + """return each participant of a respective shipcall, filtered by the shipcall id""" + # get the shipcall_participant_map + spm = self.df_dict["shipcall_participant_map"] + assigned_participants = spm.loc[spm["shipcall_id"]==shipcall.id] + return assigned_participants + + def get_assigned_participants_by_type(self, assigned_participants:pd.DataFrame, participant_type:ParticipantType): + """filters a dataframe of assigned_participants by the provided type enumerator""" + if isinstance(participant_type,int): + participant_type = ParticipantType(participant_type) + + assigned_participants_of_type = assigned_participants.loc[[participant_type in ParticipantType(int(pt_)) for pt_ in list(assigned_participants["type"].values)]] + #assigned_participants_of_type = assigned_participants.loc[assigned_participants["type"]==participant_type.value] + return assigned_participants_of_type + + def check_if_any_participant_of_type_is_unassigned(self, shipcall, *args:list[ParticipantType])->bool: + """ + given a list of input arguments, where item is a participant type, the function determines, whether at least one participant + was assigned for the type. Function returns a boolean, whether any of the required participants in unassigned. + + This method is extensively used for the validation rules 0001, where the header is checked beforehand to identify, whether + the respective participant type is assigned already. + """ + assigned_participants = self.get_assigned_participants(shipcall) + + unassigned = [] # becomes a list of booleans + for participant_type in args: + assignments_of_type = self.get_assigned_participants_by_type(assigned_participants, participant_type=participant_type) + unassignment = len(assignments_of_type)==0 # a participant type does not exist, when there is no match + unassigned.append(unassignment) + return any(unassigned) # returns a single boolean, whether ANY of the types is not assigned def standardize_model_str(self, model_str:str)->str: """check if the 'model_str' is valid and apply lowercasing to the string""" @@ -177,8 +241,28 @@ class SQLHandler(): data = data_model(**data) return data + def filter_df_by_participant_type(self, df, participant_type:typing.Union[int, ParticipantType])->pd.DataFrame: + """ + As ParticipantTypes are Flag objects, a dataframe's integer might resemble multiple participant types simultaneously. + This function allows for more complex filters, as the IntFlag allows more complex queries + + e.g.: + ParticipantType(6) is 2,4 (2+4 = 6) + + Participant(2) in Participant(6) = True # 6 is both, 2 and 4 + Participant(1) in Participant(6) = False # 6 is both, 2 and 4, but not 1 + """ + if isinstance(participant_type,int): + participant_type = ParticipantType(participant_type) + filtered_df = df.loc[[participant_type in ParticipantType(df_pt) for df_pt in list(df["participant_type"].values)]] + return filtered_df + def get_times_for_participant_type(self, df_times, participant_type:int): - filtered_series = df_times.loc[df_times["participant_type"]==participant_type] + filtered_series = self.filter_df_by_participant_type(df_times, participant_type) + #filtered_series = df_times.loc[df_times["participant_type"]==participant_type] + + if len(filtered_series)==0: + return None if not len(filtered_series)<=1: # correcting the error: ERROR:root:found multiple results @@ -239,7 +323,8 @@ class SQLHandler(): df_times = df_times.loc[~df_times[non_null_column].isnull()] # NOT null filter # filter by the agency participant_type - times_agency = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value] + times_agency = self.filter_df_by_participant_type(df_times, ParticipantType.AGENCY.value) + #times_agency = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value] return times_agency def filter_df_by_key_value(self, df, key, value)->pd.DataFrame: diff --git a/src/server/BreCal/impl/shipcalls.py b/src/server/BreCal/impl/shipcalls.py index dd9cc3f..452a8be 100644 --- a/src/server/BreCal/impl/shipcalls.py +++ b/src/server/BreCal/impl/shipcalls.py @@ -1,6 +1,6 @@ import json import logging - +import traceback import pydapper from ..schemas import model @@ -36,6 +36,7 @@ def GetShipcalls(options): pooledConnection.close() except Exception as ex: + logging.error(traceback.format_exc()) logging.error(ex) print(ex) result = {} @@ -115,6 +116,7 @@ def PostShipcalls(schemaModel): return json.dumps({"id" : new_id}), 201, {'Content-Type': 'application/json; charset=utf-8'} except Exception as ex: + logging.error(traceback.format_exc()) logging.error(ex) print(ex) result = {} @@ -199,6 +201,7 @@ def PutShipcalls(schemaModel): return json.dumps({"id" : schemaModel["id"]}), 200 except Exception as ex: + logging.error(traceback.format_exc()) logging.error(ex) print(ex) result = {} diff --git a/src/server/BreCal/validators/validation_rule_functions.py b/src/server/BreCal/validators/validation_rule_functions.py index e1c59ff..92bf24e 100644 --- a/src/server/BreCal/validators/validation_rule_functions.py +++ b/src/server/BreCal/validators/validation_rule_functions.py @@ -10,18 +10,18 @@ from BreCal.database.enums import StatusFlags # a human interpretable dictionary for error messages. In this case, the English language is preferred error_message_dict = { # 0001 A-M - "validation_rule_fct_missing_time_agency_berth_eta":"The shipcall arrives in less than 20 hours, but there are still missing times by the agency. Please add the estimated time of arrival (ETA) {Rule #0001A}", # A - "validation_rule_fct_missing_time_agency_berth_etd":"The shipcall departs in less than 20 hours, but there are still missing times by the agency. Please add the estimated time of departure (ETD) {Rule #0001B}", # B - "validation_rule_fct_missing_time_mooring_berth_eta":"The shipcall arrives in less than 16 hours, but there are still missing times by the mooring. Please add the estimated time of arrival (ETA) {Rule #0001C}", # C - "validation_rule_fct_missing_time_mooring_berth_etd":"The shipcall departs in less than 16 hours, but there are still missing times by the mooring. Please add the estimated time of departure (ETD) {Rule #0001D}", # D - "validation_rule_fct_missing_time_portadministration_berth_eta":"The shipcall arrives in less than 16 hours, but there are still missing times by the port administration. Please add the estimated time of arrival (ETA) {Rule #0001F}", # F - "validation_rule_fct_missing_time_portadministration_berth_etd":"The shipcall departs in less than 16 hours, but there are still missing times by the port administration. Please add the estimated time of departure (ETD) {Rule #0001G}", # G - "validation_rule_fct_missing_time_pilot_berth_eta":"The shipcall arrives in less than 16 hours, but there are still missing times by the pilot. Please add the estimated time of arrival (ETA) {Rule #0001H}", # H - "validation_rule_fct_missing_time_pilot_berth_etd":"The shipcall departs in less than 16 hours, but there are still missing times by the pilot. Please add the estimated time of departure (ETD) {Rule #0001I}", # I - "validation_rule_fct_missing_time_tug_berth_eta":"The shipcall arrives in less than 16 hours, but there are still missing times by the tugs. Please add the estimated time of arrival (ETA) {Rule #0001J}", # J - "validation_rule_fct_missing_time_tug_berth_etd":"The shipcall departs in less than 16 hours, but there are still missing times by the tugs. Please add the estimated time of departure (ETD) {Rule #0001K}", # K - "validation_rule_fct_missing_time_terminal_berth_eta":"The shipcall arrives in less than 16 hours, but there are still missing times by the terminal. Please add the estimated time of arrival (ETA) {Rule #0001L}", # L - "validation_rule_fct_missing_time_terminal_berth_etd":"The shipcall departs in less than 16 hours, but there are still missing times by the terminal. Please add the estimated time of departure (ETD) {Rule #0001M}", # M + "validation_rule_fct_missing_time_agency_berth_eta":"Shipcall arrives soon (<20 hours). The agency did not provide a time yet (ETA) {Rule #0001A}", # A + "validation_rule_fct_missing_time_agency_berth_etd":"Shipcall departs soon (<20 hours). The agency did not provide a time yet (ETD) {Rule #0001B}", # B + "validation_rule_fct_missing_time_mooring_berth_eta":"Shipcall arrives soon (<16 hours). The mooring did not provide a time yet (ETA) {Rule #0001C}", # C + "validation_rule_fct_missing_time_mooring_berth_etd":"Shipcall departs soon (<16 hours). The mooring did not provide a time yet (ETD) {Rule #0001D}", # D + "validation_rule_fct_missing_time_portadministration_berth_eta":"Shipcall arrives soon (<16 hours). The port administration did not provide a time yet (ETA) {Rule #0001F}", # F + "validation_rule_fct_missing_time_portadministration_berth_etd":"Shipcall departs soon (<20 hours). The port administration did not provide a time yet (ETD) {Rule #0001G}", # G + "validation_rule_fct_missing_time_pilot_berth_eta":"Shipcall arrives soon (<16 hours). The pilot did not provide a time yet (ETA) {Rule #0001H}", # H + "validation_rule_fct_missing_time_pilot_berth_etd":"Shipcall departs soon (<20 hours). The pilot did not provide a time yet (ETD) {Rule #0001I}", # I + "validation_rule_fct_missing_time_tug_berth_eta":"Shipcall arrives soon (<16 hours). The tugs did not provide a time yet (ETA) {Rule #0001J}", # J + "validation_rule_fct_missing_time_tug_berth_etd":"Shipcall departs soon (<20 hours). The tugs did not provide a time yet (ETD) {Rule #0001K}", # K + "validation_rule_fct_missing_time_terminal_berth_eta":"Shipcall arrives soon (<16 hours). The terminal did not provide a time yet (ETA) {Rule #0001L}", # L + "validation_rule_fct_missing_time_terminal_berth_etd":"Shipcall departs soon (<20 hours). The terminal did not provide a time yet (ETD) {Rule #0001M}", # M # 0002 A+B+C "validation_rule_fct_shipcall_incoming_participants_disagree_on_eta":"There are deviating times between agency, mooring, port authority, pilot and tug for the estimated time of arrival (ETA) {Rule #0002A}", @@ -99,9 +99,8 @@ class ValidationRuleBaseFunctions(): delta = self.time_logic.time_delta_from_now_to_tgt(tgt_time=query_time, unit="m") # a violation occurs, when the delta (in minutes) exceeds the specified threshold of a participant - # to prevent past-events from triggering violations, negative values are ignored - # Violation, if 0 <= delta <= threshold - violation_state = (delta >= 0) and (delta<=threshold) + # Violation, if delta <= threshold + violation_state = (delta<=threshold) return violation_state def check_participants_agree_on_estimated_time(self, shipcall, query, df_times, applicable_shipcall_type)->bool: @@ -211,14 +210,18 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): - Checks, if times_agency.eta_berth is filled in. - Measures the difference between 'now' and 'shipcall.eta'. """ - # check, if the header is filled in (agency) - if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1: + if not shipcall.type in [ShipcallType.INCOMING.value]: + return self.get_no_violation_default_output() + + # check, if the header is filled in + unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY]) + if unassigned: return self.get_no_violation_default_output() # preparation: obtain the correct times of the participant, define the query time and the key time times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) query_time = shipcall.eta - key_time = times_agency.eta_berth + key_time = times_agency.eta_berth if times_agency is not None else None threshold = ParticipantwiseTimeDelta.AGENCY violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) @@ -239,14 +242,18 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): - Checks, if times_agency.etd_berth is filled in. - Measures the difference between 'now' and 'shipcall.etd'. """ - # check, if the header is filled in (agency) - if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1: + if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]: + return self.get_no_violation_default_output() + + # check, if the header is filled in + unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY]) + if unassigned: return self.get_no_violation_default_output() # preparation: obtain the correct times of the participant, define the query time and the key time times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) query_time = shipcall.etd - key_time = times_agency.etd_berth + key_time = times_agency.etd_berth if times_agency is not None else None threshold = ParticipantwiseTimeDelta.AGENCY violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) @@ -267,16 +274,20 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): - Checks, if times_mooring.eta_berth is filled in. - Measures the difference between 'now' and 'times_agency.eta_berth'. """ - # check, if the header is filled in (agency & MOORING) - if (len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) != 1) or (len(df_times.loc[df_times["participant_type"]==ParticipantType.MOORING.value]) != 1): + if not shipcall.type in [ShipcallType.INCOMING.value]: return self.get_no_violation_default_output() + # check, if the header is filled in + unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.MOORING]) + if unassigned: + return self.get_no_violation_default_output() + # preparation: obtain the correct times of the participant, define the query time and the key time times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) times_mooring = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.MOORING.value) - query_time = times_agency.eta_berth - key_time = times_mooring.eta_berth + query_time = times_agency.eta_berth if times_agency is not None else None + key_time = times_mooring.eta_berth if times_mooring is not None else None threshold = ParticipantwiseTimeDelta.MOORING violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) @@ -297,16 +308,20 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): - Checks, if times_mooring.etd_berth is filled in. - Measures the difference between 'now' and 'times_agency.etd_berth'. """ - # check, if the header is filled in (agency & MOORING) - if (len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) != 1) or (len(df_times.loc[df_times["participant_type"]==ParticipantType.MOORING.value]) != 1): + if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]: return self.get_no_violation_default_output() + # check, if the header is filled in + unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.MOORING]) + if unassigned: + return self.get_no_violation_default_output() + # preparation: obtain the correct times of the participant, define the query time and the key time times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) times_mooring = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.MOORING.value) - query_time = times_agency.etd_berth - key_time = times_mooring.etd_berth + query_time = times_agency.etd_berth if times_agency is not None else None + key_time = times_mooring.etd_berth if times_mooring is not None else None threshold = ParticipantwiseTimeDelta.MOORING violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) @@ -327,16 +342,20 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): - Checks, if times_port_administration.eta_berth is filled in. - Measures the difference between 'now' and 'times_agency.eta_berth'. """ - # check, if the header is filled in (agency & PORT_ADMINISTRATION) - if (len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) != 1) or (len(df_times.loc[df_times["participant_type"]==ParticipantType.PORT_ADMINISTRATION.value]) != 1): + if not shipcall.type in [ShipcallType.INCOMING.value]: + return self.get_no_violation_default_output() + + # check, if the header is filled in + unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.PORT_ADMINISTRATION]) + if unassigned: return self.get_no_violation_default_output() # preparation: obtain the correct times of the participant, define the query time and the key time times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) times_port_administration = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.PORT_ADMINISTRATION.value) - query_time = times_agency.eta_berth - key_time = times_port_administration.eta_berth + query_time = times_agency.eta_berth if times_agency is not None else None + key_time = times_port_administration.eta_berth if times_port_administration is not None else None threshold = ParticipantwiseTimeDelta.PORT_ADMINISTRATION violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) @@ -357,16 +376,21 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): - Checks, if times_port_administration.etd_berth is filled in. - Measures the difference between 'now' and 'times_agency.etd_berth'. """ - # check, if the header is filled in (agency & PORT_ADMINISTRATION) - if (len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) != 1) or (len(df_times.loc[df_times["participant_type"]==ParticipantType.PORT_ADMINISTRATION.value]) != 1): + if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]: + return self.get_no_violation_default_output() + + # check, if the header is filled in + unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.PORT_ADMINISTRATION]) + if unassigned: return self.get_no_violation_default_output() # preparation: obtain the correct times of the participant, define the query time and the key time + # when there are no times, the function returns None times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) times_port_administration = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.PORT_ADMINISTRATION.value) - query_time = times_agency.etd_berth - key_time = times_port_administration.etd_berth + query_time = times_agency.etd_berth if times_agency is not None else None + key_time = times_port_administration.etd_berth if times_port_administration is not None else None threshold = ParticipantwiseTimeDelta.PORT_ADMINISTRATION violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) @@ -387,16 +411,20 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): - Checks, if times_pilot.eta_berth is filled in. - Measures the difference between 'now' and 'times_agency.eta_berth'. """ - # check, if the header is filled in (agency & PILOT) - if (len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) != 1) or (len(df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value]) != 1): + if not shipcall.type in [ShipcallType.INCOMING.value]: + return self.get_no_violation_default_output() + + # check, if the header is filled in + unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.PILOT]) + if unassigned: return self.get_no_violation_default_output() # preparation: obtain the correct times of the participant, define the query time and the key time times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) times_pilot = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.PILOT.value) - query_time = times_agency.eta_berth - key_time = times_pilot.eta_berth + query_time = times_agency.eta_berth if times_agency is not None else None + key_time = times_pilot.eta_berth if times_pilot is not None else None threshold = ParticipantwiseTimeDelta.PILOT violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) @@ -417,16 +445,20 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): - Checks, if times_pilot.etd_berth is filled in. - Measures the difference between 'now' and 'times_agency.etd_berth'. """ - # check, if the header is filled in (agency & PILOT) - if (len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) != 1) or (len(df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value]) != 1): + if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]: + return self.get_no_violation_default_output() + + # check, if the header is filled in + unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.PILOT]) + if unassigned: return self.get_no_violation_default_output() # preparation: obtain the correct times of the participant, define the query time and the key time times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) times_pilot = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.PILOT.value) - query_time = times_agency.etd_berth - key_time = times_pilot.etd_berth + query_time = times_agency.etd_berth if times_agency is not None else None + key_time = times_pilot.etd_berth if times_pilot is not None else None threshold = ParticipantwiseTimeDelta.PILOT violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) @@ -447,16 +479,20 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): - Checks, if times_tug.eta_berth is filled in. - Measures the difference between 'now' and 'times_agency.eta_berth'. """ - # check, if the header is filled in (agency & TUG) - if (len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) != 1) or (len(df_times.loc[df_times["participant_type"]==ParticipantType.TUG.value]) != 1): + if not shipcall.type in [ShipcallType.INCOMING.value]: + return self.get_no_violation_default_output() + + # check, if the header is filled in + unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.TUG]) + if unassigned: return self.get_no_violation_default_output() # preparation: obtain the correct times of the participant, define the query time and the key time times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) times_tug = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TUG.value) - query_time = times_agency.eta_berth - key_time = times_tug.eta_berth + query_time = times_agency.eta_berth if times_agency is not None else None + key_time = times_tug.eta_berth if times_tug is not None else None threshold = ParticipantwiseTimeDelta.TUG violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) @@ -477,16 +513,20 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): - Checks, if times_tug.etd_berth is filled in. - Measures the difference between 'now' and 'times_agency.etd_berth'. """ - # check, if the header is filled in (agency & TUG) - if (len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) != 1) or (len(df_times.loc[df_times["participant_type"]==ParticipantType.TUG.value]) != 1): + if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]: + return self.get_no_violation_default_output() + + # check, if the header is filled in + unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.TUG]) + if unassigned: return self.get_no_violation_default_output() # preparation: obtain the correct times of the participant, define the query time and the key time times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) times_tug = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TUG.value) - query_time = times_agency.etd_berth - key_time = times_tug.etd_berth + query_time = times_agency.etd_berth if times_agency is not None else None + key_time = times_tug.etd_berth if times_tug is not None else None threshold = ParticipantwiseTimeDelta.TUG violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) @@ -507,16 +547,20 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): - Checks, if times_terminal.operations_start is filled in. - Measures the difference between 'now' and 'times_agency.eta_berth'. """ - # check, if the header is filled in (agency & terminal) - if (len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) != 1) or (len(df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value]) != 1): - return self.get_no_violation_default_output() # rule not applicable + if not shipcall.type in [ShipcallType.INCOMING.value]: + return self.get_no_violation_default_output() + + # check, if the header is filled in + unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.TERMINAL]) + if unassigned: + return self.get_no_violation_default_output() # preparation: obtain the correct times of the participant, define the query time and the key time times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) times_terminal = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TERMINAL.value) - query_time = times_agency.eta_berth - key_time = times_terminal.operations_start # eta_berth does not exist in times_terminal! Instead, it is called operations_start + query_time = times_agency.eta_berth if times_agency is not None else None + key_time = times_terminal.operations_start if times_terminal is not None else None # eta_berth does not exist in times_terminal! Instead, it is called operations_start threshold = ParticipantwiseTimeDelta.TERMINAL violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) @@ -537,19 +581,20 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): - Checks, if times_terminal.operations_end is filled in. - Measures the difference between 'now' and 'times_agency.etd_berth'. """ - # check, if the header is filled in (agency & terminal) - if len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) != 1: - return self.get_no_violation_default_output() # rule not applicable - - if len(df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value]) != 1: - return self.get_no_violation_default_output() # rule not applicable + if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]: + return self.get_no_violation_default_output() + + # check, if the header is filled in + unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.TERMINAL]) + if unassigned: + return self.get_no_violation_default_output() # preparation: obtain the correct times of the participant, define the query time and the key time times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) times_terminal = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TERMINAL.value) - query_time = times_agency.etd_berth - key_time = times_terminal.operations_end # etd_berth does not exist in times_terminal! Instead, it is called operations_end + query_time = times_agency.etd_berth if times_agency is not None else None + key_time = times_terminal.operations_end if times_terminal is not None else None # etd_berth does not exist in times_terminal! Instead, it is called operations_end threshold = ParticipantwiseTimeDelta.TERMINAL violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) @@ -651,6 +696,9 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): query time: eta_berth (times_agency) start_time & end_time: operations_start & operations_end (times_terminal) """ + if not shipcall.type in [ShipcallType.INCOMING.value]: + return self.get_no_violation_default_output() + # check, if the header is filled in (agency & terminal) if len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) != 1: return self.get_no_violation_default_output() # rule not applicable @@ -683,6 +731,9 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): query time: eta_berth (times_agency) start_time & end_time: operations_start & operations_end (times_terminal) """ + if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]: + return self.get_no_violation_default_output() + # check, if the header is filled in (agency & terminal) if len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) != 1: return self.get_no_violation_default_output() # rule not applicable @@ -715,6 +766,9 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): query time: eta_berth (times_agency) start_time & end_time: tidal_window_from & tidal_window_to (shipcall) """ + if not shipcall.type in [ShipcallType.INCOMING.value]: + return self.get_no_violation_default_output() + # check, if the header is filled in (agency) if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1: return self.get_no_violation_default_output() @@ -743,6 +797,9 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): query time: eta_berth (times_agency) start_time & end_time: tidal_window_from & tidal_window_to (shipcall) """ + if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]: + return self.get_no_violation_default_output() + # check, if the header is filled in (agency) if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1: return self.get_no_violation_default_output() @@ -857,18 +914,18 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions): times_terminal = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TERMINAL.value) # when one of the two values is null, the state is GREEN - if (times_agency.pier_side is None) or (times_terminal.pier_side is None): + if (shipcall.pier_side is None) or (times_terminal.pier_side is None): return self.get_no_violation_default_output() # when one of the two values is null, the state is GREEN - if (pd.isnull(times_agency.pier_side)) or (pd.isnull(times_terminal.pier_side)): + if (pd.isnull(shipcall.pier_side)) or (pd.isnull(times_terminal.pier_side)): return self.get_no_violation_default_output() # only incoming shipcalls matter. The other ones are not relevant for the pier_side selection if shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]: return self.get_no_violation_default_output() - violation_state = bool(times_agency.pier_side)!=bool(times_terminal.pier_side) + violation_state = bool(shipcall.pier_side)!=bool(times_terminal.pier_side) if violation_state: validation_name = "validation_rule_fct_agency_and_terminal_pier_side_disagreement" diff --git a/src/server/BreCal/validators/validation_rules.py b/src/server/BreCal/validators/validation_rules.py index 2753446..aa2ef22 100644 --- a/src/server/BreCal/validators/validation_rules.py +++ b/src/server/BreCal/validators/validation_rules.py @@ -1,4 +1,5 @@ import copy +import re import numpy as np import pandas as pd from BreCal.database.enums import StatusFlags @@ -29,7 +30,14 @@ class ValidationRules(ValidationRuleFunctions): returns: (evaluation_state, violations) """ # prepare df_times, which every validation rule tends to use - df_times = self.sql_handler.df_dict.get('times') # -> pd.DataFrame + df_times = self.sql_handler.df_dict.get('times', pd.DataFrame()) # -> pd.DataFrame + + if len(df_times)==0: + return (StatusFlags.GREEN.value, []) + + spm = self.sql_handler.df_dict["shipcall_participant_map"] + if len(spm.loc[spm["shipcall_id"]==shipcall.id])==0: + return (StatusFlags.GREEN.value, []) # filter by shipcall id df_times = self.sql_handler.get_times_of_shipcall(shipcall) @@ -43,15 +51,9 @@ class ValidationRules(ValidationRuleFunctions): # 'translate' all error codes into readable, human-understandable format. evaluation_results = [(state, self.describe_error_message(msg)) for (state, msg) in evaluation_results] - - """ # deprecated - # check, if ANY of the evaluation results (evaluation_state) is larger than the .GREEN state. This means, that .YELLOW and .RED - # would return 'True'. Numpy arrays and functions are used to accelerate the comparison. - # np.any returns a boolean. - #evaluation_state = not np.any(np.greater(np.array([result[0] for result in evaluation_results]), ValidationRuleState.GREEN)) - """ + # check, what the maximum state flag is and return it - evaluation_state = np.max(np.array([result[0].value for result in evaluation_results])) if len(evaluation_results)>0 else 1 + evaluation_state = np.max(np.array([result[0].value for result in evaluation_results])) if len(evaluation_results)>0 else StatusFlags.GREEN.value evaluation_verbosity = [result[1] for result in evaluation_results] return (evaluation_state, evaluation_verbosity) @@ -74,11 +76,27 @@ class ValidationRules(ValidationRuleFunctions): # unbundle individual results. evaluation_state becomes an integer, violation evaluation_state = [StatusFlags(res[0]).value for res in results] - violations = [",".join(res[1]) if len(res[1])>0 else None for res in results] + violations = [",\r\n".join(res[1]) if len(res[1])>0 else None for res in results] + violations = [self.concise_evaluation_message_if_too_long(violation) for violation in violations] shipcall_df.loc[:,"evaluation"] = evaluation_state shipcall_df.loc[:,"evaluation_message"] = violations return shipcall_df + + def concise_evaluation_message_if_too_long(self, violation): + """ + when many validation rules are violated at once, the resulting evaluation message may exceed 512 characters (which the mysql database allows) + in these cases, use a regular expression to provide a concise message, where the 'concise' description is only the list of violated rools + """ + if violation is None: + return violation + + if len(violation)>=512: + concise = re.findall(r'{(.*?)\}', violation) + + # e.g.: Evaluation message too long. Violated Rules: ['Rule #0001C', 'Rule #0001H', 'Rule #0001F', 'Rule #0001G', 'Rule #0001L', 'Rule #0001M', 'Rule #0001J', 'Rule #0001K'] + violation = f"Evaluation message too long. Violated Rules: {concise}" + return violation def determine_validation_state(self) -> str: """ diff --git a/src/server/tests/validators/test_validation_rule_functions.py b/src/server/tests/validators/test_validation_rule_functions.py index 1847973..2593e85 100644 --- a/src/server/tests/validators/test_validation_rule_functions.py +++ b/src/server/tests/validators/test_validation_rule_functions.py @@ -117,6 +117,19 @@ def test_validation_rule_fct_missing_time_agency_berth_eta__shipcall_eta_dangero # set times agency to be undetermined df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = None + # must adapt the shipcall_participant_map, so it suits the test + agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0] + + spm = vr.sql_handler.df_dict["shipcall_participant_map"] + df = pd.DataFrame( + [ + {"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}, + ] + ) + df.set_index("id", inplace=True) + spm = pd.concat([spm, df], axis=0, ignore_index=True) + vr.sql_handler.df_dict["shipcall_participant_map"] = spm + # apply the validation rule (state, msg) = vr.validation_rule_fct_missing_time_agency_berth_eta(shipcall=shipcall, df_times=df_times) @@ -137,6 +150,19 @@ def test_validation_rule_fct_missing_time_agency_berth_eta__shipcall_eta_distant # set times agency to be undetermined df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = None + # must adapt the shipcall_participant_map, so it suits the test + agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0] + + spm = vr.sql_handler.df_dict["shipcall_participant_map"] + df = pd.DataFrame( + [ + {"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}, + ] + ) + df.set_index("id", inplace=True) + spm = pd.concat([spm, df], axis=0, ignore_index=True) + vr.sql_handler.df_dict["shipcall_participant_map"] = spm + # apply the validation rule (state, msg) = vr.validation_rule_fct_missing_time_agency_berth_eta(shipcall=shipcall, df_times=df_times) @@ -157,6 +183,19 @@ def test_validation_rule_fct_missing_time_agency_berth_eta__shipcall_eta_is_unde # set times agency to be undetermined df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = None + # must adapt the shipcall_participant_map, so it suits the test + agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0] + + spm = vr.sql_handler.df_dict["shipcall_participant_map"] + df = pd.DataFrame( + [ + {"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}, + ] + ) + df.set_index("id", inplace=True) + spm = pd.concat([spm, df], axis=0, ignore_index=True) + vr.sql_handler.df_dict["shipcall_participant_map"] = spm + # apply the validation rule (state, msg) = vr.validation_rule_fct_missing_time_agency_berth_eta(shipcall=shipcall, df_times=df_times) @@ -172,11 +211,25 @@ def test_validation_rule_fct_missing_time_agency_berth_etd__shipcall_etd_is_unde df_times = get_df_times(shipcall) # the shipcall etd is 'soon' + shipcall.type = ShipcallType.OUTGOING.value 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 + # must adapt the shipcall_participant_map, so it suits the test + agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0] + + spm = vr.sql_handler.df_dict["shipcall_participant_map"] + df = pd.DataFrame( + [ + {"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}, + ] + ) + df.set_index("id", inplace=True) + spm = pd.concat([spm, df], axis=0, ignore_index=True) + vr.sql_handler.df_dict["shipcall_participant_map"] = spm + # apply the validation rule (state, msg) = vr.validation_rule_fct_missing_time_agency_berth_etd(shipcall=shipcall, df_times=df_times) @@ -197,6 +250,21 @@ def test_validation_rule_fct_missing_time_mooring_berth_eta__shipcall_soon_but_p # set times agency to be undetermined df_times.loc[df_times["participant_type"]==ParticipantType.MOORING.value, "eta_berth"] = None + # must adapt the shipcall_participant_map, so it suits the test + agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0] + mooring_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.MOORING.value, "participant_id"].iloc[0] + + spm = vr.sql_handler.df_dict["shipcall_participant_map"] + df = pd.DataFrame( + [ + {"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}, + {"id":10002, "shipcall_id":shipcall.id, "participant_id":mooring_participant_id, "type":ParticipantType.MOORING.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None} + ] + ) + df.set_index("id", inplace=True) + spm = pd.concat([spm, df], axis=0, ignore_index=True) + vr.sql_handler.df_dict["shipcall_participant_map"] = spm + # apply the validation rule (state, msg) = vr.validation_rule_fct_missing_time_mooring_berth_eta(shipcall=shipcall, df_times=df_times) @@ -211,6 +279,7 @@ def test_validation_rule_fct_missing_time_mooring_berth_etd__shipcall_soon_but_p vr = build_sql_proxy_connection['vr'] shipcall = get_shipcall_simple() + shipcall.type = ShipcallType.OUTGOING.value df_times = get_df_times(shipcall) # according to the agency, a shipcall takes place soon (ETA/ETD) @@ -219,6 +288,21 @@ def test_validation_rule_fct_missing_time_mooring_berth_etd__shipcall_soon_but_p # set times agency to be undetermined df_times.loc[df_times["participant_type"]==ParticipantType.MOORING.value, "etd_berth"] = None + # must adapt the shipcall_participant_map, so it suits the test + agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0] + mooring_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.MOORING.value, "participant_id"].iloc[0] + + spm = vr.sql_handler.df_dict["shipcall_participant_map"] + df = pd.DataFrame( + [ + {"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}, + {"id":10002, "shipcall_id":shipcall.id, "participant_id":mooring_participant_id, "type":ParticipantType.MOORING.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None} + ] + ) + df.set_index("id", inplace=True) + spm = pd.concat([spm, df], axis=0, ignore_index=True) + vr.sql_handler.df_dict["shipcall_participant_map"] = spm + # apply the validation rule (state, msg) = vr.validation_rule_fct_missing_time_mooring_berth_etd(shipcall=shipcall, df_times=df_times) @@ -241,6 +325,21 @@ def test_validation_rule_fct_missing_time_portadministration_berth_eta__shipcall # set times agency to be undetermined df_times.loc[df_times["participant_type"]==ParticipantType.PORT_ADMINISTRATION.value, "eta_berth"] = None + # must adapt the shipcall_participant_map, so it suits the test + agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0] + pa_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.PORT_ADMINISTRATION.value, "participant_id"].iloc[0] + + spm = vr.sql_handler.df_dict["shipcall_participant_map"] + df = pd.DataFrame( + [ + {"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}, + {"id":10002, "shipcall_id":shipcall.id, "participant_id":pa_participant_id, "type":ParticipantType.PORT_ADMINISTRATION.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None} + ] + ) + df.set_index("id", inplace=True) + spm = pd.concat([spm, df], axis=0, ignore_index=True) + vr.sql_handler.df_dict["shipcall_participant_map"] = spm + # apply the validation rule (state, msg) = vr.validation_rule_fct_missing_time_portadministration_berth_eta(shipcall=shipcall, df_times=df_times) @@ -255,6 +354,7 @@ def test_validation_rule_fct_missing_time_portadministration_berth_etd__shipcall vr = build_sql_proxy_connection['vr'] shipcall = get_shipcall_simple() + shipcall.type = ShipcallType.SHIFTING.value df_times = get_df_times(shipcall) # according to the agency, a shipcall takes place soon (ETA/ETD) @@ -263,6 +363,21 @@ def test_validation_rule_fct_missing_time_portadministration_berth_etd__shipcall # set times agency to be undetermined df_times.loc[df_times["participant_type"]==ParticipantType.PORT_ADMINISTRATION.value, "etd_berth"] = None + # must adapt the shipcall_participant_map, so it suits the test + agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0] + pa_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.PORT_ADMINISTRATION.value, "participant_id"].iloc[0] + + spm = vr.sql_handler.df_dict["shipcall_participant_map"] + df = pd.DataFrame( + [ + {"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}, + {"id":10002, "shipcall_id":shipcall.id, "participant_id":pa_participant_id, "type":ParticipantType.PORT_ADMINISTRATION.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None} + ] + ) + df.set_index("id", inplace=True) + spm = pd.concat([spm, df], axis=0, ignore_index=True) + vr.sql_handler.df_dict["shipcall_participant_map"] = spm + # apply the validation rule (state, msg) = vr.validation_rule_fct_missing_time_portadministration_berth_etd(shipcall=shipcall, df_times=df_times) @@ -283,6 +398,21 @@ def test_validation_rule_fct_missing_time_pilot_berth_eta__shipcall_soon_but_par # set times agency to be undetermined df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, "eta_berth"] = None + # must adapt the shipcall_participant_map, so it suits the test + agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0] + pilot_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, "participant_id"].iloc[0] + + spm = vr.sql_handler.df_dict["shipcall_participant_map"] + df = pd.DataFrame( + [ + {"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}, + {"id":10002, "shipcall_id":shipcall.id, "participant_id":pilot_participant_id, "type":ParticipantType.PILOT.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None} + ] + ) + df.set_index("id", inplace=True) + spm = pd.concat([spm, df], axis=0, ignore_index=True) + vr.sql_handler.df_dict["shipcall_participant_map"] = spm + # apply the validation rule (state, msg) = vr.validation_rule_fct_missing_time_pilot_berth_eta(shipcall=shipcall, df_times=df_times) @@ -295,6 +425,7 @@ def test_validation_rule_fct_missing_time_pilot_berth_etd__shipcall_soon_but_par vr = build_sql_proxy_connection['vr'] shipcall = get_shipcall_simple() + shipcall.type = ShipcallType.OUTGOING.value df_times = get_df_times(shipcall) # according to the agency, a shipcall takes place soon (ETA/ETD) @@ -303,6 +434,21 @@ def test_validation_rule_fct_missing_time_pilot_berth_etd__shipcall_soon_but_par # set times agency to be undetermined df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, "etd_berth"] = None + # must adapt the shipcall_participant_map, so it suits the test + agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0] + pilot_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, "participant_id"].iloc[0] + + spm = vr.sql_handler.df_dict["shipcall_participant_map"] + df = pd.DataFrame( + [ + {"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}, + {"id":10002, "shipcall_id":shipcall.id, "participant_id":pilot_participant_id, "type":ParticipantType.PILOT.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None} + ] + ) + df.set_index("id", inplace=True) + spm = pd.concat([spm, df], axis=0, ignore_index=True) + vr.sql_handler.df_dict["shipcall_participant_map"] = spm + # apply the validation rule (state, msg) = vr.validation_rule_fct_missing_time_pilot_berth_etd(shipcall=shipcall, df_times=df_times) @@ -310,13 +456,127 @@ def test_validation_rule_fct_missing_time_pilot_berth_etd__shipcall_soon_but_par 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_unassigned__return_green(build_sql_proxy_connection): + """0001-I validation_rule_fct_missing_time_pilot_berth_etd""" + vr = build_sql_proxy_connection['vr'] + shipcall = get_shipcall_simple() + shipcall.type = ShipcallType.OUTGOING.value + 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 + + # must adapt the shipcall_participant_map, so it suits the test + agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0] + + vr.sql_handler.read_all(vr.sql_handler.all_schemas) + spm = vr.sql_handler.df_dict["shipcall_participant_map"] + df = pd.DataFrame( + [ + {"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None} + ] + ) + df.set_index("id", inplace=True) + spm = pd.concat([spm, df], axis=0, ignore_index=True) + vr.sql_handler.df_dict["shipcall_participant_map"] = spm + + # 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.GREEN, f"function should return 'green', because the pilot is not assigned yet" + return + +def test_validation_rule_fct_missing_time_pilot_berth_etd__shipcall_soon_but_participant_estimated_time_undefined_multiple_pilot_assignments_due_to_bug(build_sql_proxy_connection): + """ + 0001-I validation_rule_fct_missing_time_pilot_berth_etd. Checks, whether the function still works in case of a buggy input. When there is more than one pilot + assignment, the validation rule should still work and return 'yellow' properly. + """ + vr = build_sql_proxy_connection['vr'] + + shipcall = get_shipcall_simple() + shipcall.type = ShipcallType.OUTGOING.value + 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 + + # must adapt the shipcall_participant_map, so it suits the test + agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0] + pilot_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, "participant_id"].iloc[0] + + spm = vr.sql_handler.df_dict["shipcall_participant_map"] + df = pd.DataFrame( + [ + {"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}, + {"id":10002, "shipcall_id":shipcall.id, "participant_id":pilot_participant_id, "type":ParticipantType.PILOT.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}, + {"id":10003, "shipcall_id":shipcall.id, "participant_id":pilot_participant_id, "type":ParticipantType.PILOT.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}, + {"id":10004, "shipcall_id":shipcall.id, "participant_id":pilot_participant_id, "type":ParticipantType.PILOT.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None} + ] + ) + df.set_index("id", inplace=True) + spm = pd.concat([spm, df], axis=0, ignore_index=True) + vr.sql_handler.df_dict["shipcall_participant_map"] = spm + + # 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_pilot_berth_etd__agency_and_pilot_assigned_pilot_no_times_returns_yellow(build_sql_proxy_connection): + """ + 0001-I validation_rule_fct_missing_time_pilot_berth_etd. Checks the default behaviour, where an agency's time might exist, + while a time by pilot may not exist. In these cases, a yellow state is expected. + """ + vr = build_sql_proxy_connection['vr'] + + shipcall = get_shipcall_simple() + shipcall.type = ShipcallType.OUTGOING.value + 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) + + # must adapt the shipcall_participant_map, so it suits the test + agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0] + pilot_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, "participant_id"].iloc[0] + + # set times of PILOT: should not exist + df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value,"participant_type"] = ParticipantType.BSMD.value + + spm = vr.sql_handler.df_dict["shipcall_participant_map"] + df = pd.DataFrame( + [ + {"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}, + {"id":10002, "shipcall_id":shipcall.id, "participant_id":pilot_participant_id, "type":ParticipantType.PILOT.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None} + ] + ) + df.set_index("id", inplace=True) + spm = pd.concat([spm, df], axis=0, ignore_index=True) + vr.sql_handler.df_dict["shipcall_participant_map"] = spm + + # 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() + shipcall.type = ShipcallType.INCOMING.value df_times = get_df_times(shipcall) # according to the agency, a shipcall takes place soon (ETA/ETD) @@ -325,6 +585,21 @@ def test_validation_rule_fct_missing_time_tug_berth_eta__shipcall_soon_but_parti # set times agency to be undetermined df_times.loc[df_times["participant_type"]==ParticipantType.TUG.value, "eta_berth"] = None + # must adapt the shipcall_participant_map, so it suits the test + agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0] + tug_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.TUG.value, "participant_id"].iloc[0] + + spm = vr.sql_handler.df_dict["shipcall_participant_map"] + df = pd.DataFrame( + [ + {"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}, + {"id":10002, "shipcall_id":shipcall.id, "participant_id":tug_participant_id, "type":ParticipantType.TUG.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None} + ] + ) + df.set_index("id", inplace=True) + spm = pd.concat([spm, df], axis=0, ignore_index=True) + vr.sql_handler.df_dict["shipcall_participant_map"] = spm + # apply the validation rule (state, msg) = vr.validation_rule_fct_missing_time_tug_berth_eta(shipcall=shipcall, df_times=df_times) @@ -339,6 +614,7 @@ def test_validation_rule_fct_missing_time_tug_berth_etd__shipcall_soon_but_parti vr = build_sql_proxy_connection['vr'] shipcall = get_shipcall_simple() + shipcall.type = ShipcallType.OUTGOING.value df_times = get_df_times(shipcall) # according to the agency, a shipcall takes place soon (ETA/ETD) @@ -347,6 +623,21 @@ def test_validation_rule_fct_missing_time_tug_berth_etd__shipcall_soon_but_parti # set times agency to be undetermined df_times.loc[df_times["participant_type"]==ParticipantType.TUG.value, "etd_berth"] = None + # must adapt the shipcall_participant_map, so it suits the test + agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0] + tug_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.TUG.value, "participant_id"].iloc[0] + + spm = vr.sql_handler.df_dict["shipcall_participant_map"] + df = pd.DataFrame( + [ + {"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}, + {"id":10002, "shipcall_id":shipcall.id, "participant_id":tug_participant_id, "type":ParticipantType.TUG.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None} + ] + ) + df.set_index("id", inplace=True) + spm = pd.concat([spm, df], axis=0, ignore_index=True) + vr.sql_handler.df_dict["shipcall_participant_map"] = spm + # apply the validation rule (state, msg) = vr.validation_rule_fct_missing_time_tug_berth_etd(shipcall=shipcall, df_times=df_times) @@ -369,6 +660,21 @@ def test_validation_rule_fct_missing_time_terminal_berth_eta__shipcall_soon_but_ # set times agency to be undetermined df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "operations_start"] = None # previously: eta_berth, which does not exist in times_terminal + # must adapt the shipcall_participant_map, so it suits the test + agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0] + terminal_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "participant_id"].iloc[0] + + spm = vr.sql_handler.df_dict["shipcall_participant_map"] + df = pd.DataFrame( + [ + {"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}, + {"id":10002, "shipcall_id":shipcall.id, "participant_id":terminal_participant_id, "type":ParticipantType.TERMINAL.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None} + ] + ) + df.set_index("id", inplace=True) + spm = pd.concat([spm, df], axis=0, ignore_index=True) + vr.sql_handler.df_dict["shipcall_participant_map"] = spm + # apply the validation rule (state, msg) = vr.validation_rule_fct_missing_time_terminal_berth_eta(shipcall=shipcall, df_times=df_times) @@ -383,6 +689,7 @@ def test_validation_rule_fct_missing_time_terminal_berth_etd__shipcall_soon_but_ vr = build_sql_proxy_connection['vr'] shipcall = get_shipcall_simple() + shipcall.type = ShipcallType.OUTGOING.value df_times = get_df_times(shipcall) # according to the agency, a shipcall takes place soon (ETA/ETD) @@ -391,6 +698,22 @@ def test_validation_rule_fct_missing_time_terminal_berth_etd__shipcall_soon_but_ # set times agency to be undetermined df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "operations_end"] = None # previously: etd_berth, which does not exist in times_terminal + + # must adapt the shipcall_participant_map, so it suits the test + agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0] + terminal_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "participant_id"].iloc[0] + + spm = vr.sql_handler.df_dict["shipcall_participant_map"] + df = pd.DataFrame( + [ + {"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}, + {"id":10002, "shipcall_id":shipcall.id, "participant_id":terminal_participant_id, "type":ParticipantType.TERMINAL.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None} + ] + ) + df.set_index("id", inplace=True) + spm = pd.concat([spm, df], axis=0, ignore_index=True) + vr.sql_handler.df_dict["shipcall_participant_map"] = spm + # apply the validation rule (state, msg) = vr.validation_rule_fct_missing_time_terminal_berth_etd(shipcall=shipcall, df_times=df_times) @@ -602,6 +925,7 @@ def test_validation_rule_fct_etd_time_not_in_operation_window__times_dont_match( """0003-B validation_rule_fct_etd_time_not_in_operation_window""" vr = build_sql_proxy_connection['vr'] shipcall = get_shipcall_simple() + shipcall.type = ShipcallType.SHIFTING.value df_times = get_df_times(shipcall) t0_time = datetime.datetime.now() # reference time for easier readability @@ -623,6 +947,7 @@ def test_validation_rule_fct_eta_time_not_in_operation_window_and_validation_rul vr = build_sql_proxy_connection['vr'] import random shipcall = get_shipcall_simple() + shipcall.type = ShipcallType.SHIFTING.value df_times = get_df_times(shipcall) t0_time = datetime.datetime.now() @@ -718,6 +1043,7 @@ def test_validation_rule_fct_etd_time_not_in_tidal_window__etd_outside_tidal_win """0004-B validation_rule_fct_etd_time_not_in_tidal_window""" vr = build_sql_proxy_connection['vr'] shipcall = get_shipcall_simple() + shipcall.type = ShipcallType.OUTGOING.value df_times = get_df_times(shipcall) t0_time = datetime.datetime.now() @@ -801,7 +1127,8 @@ def test_validation_rule_fct_agency_and_terminal_pier_side_disagreement__agency_ 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 + shipcall.pier_side = True + # 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) @@ -813,7 +1140,8 @@ def test_validation_rule_fct_agency_and_terminal_pier_side_disagreement__agency_ 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 + shipcall.pier_side = True + #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) @@ -825,7 +1153,8 @@ def test_validation_rule_fct_agency_and_terminal_pier_side_disagreement__agency_ 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 + shipcall.pier_side = True + # 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"] = None (code, msg) = vr.validation_rule_fct_agency_and_terminal_pier_side_disagreement(shipcall=shipcall, df_times=df_times) @@ -865,7 +1194,8 @@ def test_validation_rule_fct_agency_and_terminal_pier_side_agreement(build_sql_p t2.participant_type = ParticipantType.TERMINAL.value # agreement - t1.pier_side = True + shipcall.pier_side = True + # t1.pier_side = True t2.pier_side = True time_objects = [t1, t2] @@ -896,7 +1226,8 @@ def test_validation_rule_fct_agency_and_terminal_pier_side_disagreement(build_sq t2.participant_type = ParticipantType.TERMINAL.value # disagreement - t1.pier_side = True + shipcall.pier_side = True + # t1.pier_side = True t2.pier_side = False time_objects = [t1, t2]