changing the ParticipantType to an IntFlag, so multiple roles are possible. Adapting every validation rule (0001, 0003, 0004, 0005), which may be affected by this change. Changing the filter for a participant type to properly include the change. Changing the pier_side rule (0006B), which uses the shipcall and times_terminal. New shipcalls should now be evaluated properly, unless no participant is assigned at all. If the ladder case can occur, the validation rules 0001N+0001O will be added (held back for now).

This commit is contained in:
scopesorting 2023-11-30 15:53:42 +01:00
parent 8e9be1eae5
commit dc79f05b8b
5 changed files with 52 additions and 18 deletions

View File

@ -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""" """determines the type of a participant"""
NONE = 0 NONE = 0
BSMD = 1 BSMD = 1

View File

@ -1,6 +1,7 @@
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import datetime import datetime
import typing
from BreCal.schemas.model import Shipcall, Ship, Participant, Berth, User, Times from BreCal.schemas.model import Shipcall, Ship, Participant, Berth, User, Times
from BreCal.database.enums import ParticipantType from BreCal.database.enums import ParticipantType
@ -110,7 +111,8 @@ class SQLHandler():
self.initialize_shipcall_participant_list() self.initialize_shipcall_participant_list()
# update the 'type' in shipcall_participants_map # update the 'type' in shipcall_participants_map
self.add_participant_type_to_map() # fully deprecated
# self.add_participant_type_to_map()
return return
def build_full_mysql_df_dict(self, all_schemas): def build_full_mysql_df_dict(self, all_schemas):
@ -143,11 +145,12 @@ class SQLHandler():
applies a lambda function, where the 'type'-column in the shipcall_participant_map is updated by reading the 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. respective data from the participants. Updates the shipcall_participant_map inplace.
""" """
spm = self.df_dict["shipcall_participant_map"] 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.")
participant_df = self.df_dict["participant"] #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) #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 #self.df_dict["shipcall_participant_map"] = spm
return return
def get_assigned_participants(self, shipcall)->pd.DataFrame: def get_assigned_participants(self, shipcall)->pd.DataFrame:
@ -159,7 +162,11 @@ class SQLHandler():
def get_assigned_participants_by_type(self, assigned_participants:pd.DataFrame, participant_type:ParticipantType): 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""" """filters a dataframe of assigned_participants by the provided type enumerator"""
assigned_participants_of_type = assigned_participants.loc[assigned_participants["type"]==participant_type.value] 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 return assigned_participants_of_type
def check_if_any_participant_of_type_is_unassigned(self, shipcall, *args:list[ParticipantType])->bool: def check_if_any_participant_of_type_is_unassigned(self, shipcall, *args:list[ParticipantType])->bool:
@ -234,8 +241,25 @@ class SQLHandler():
data = data_model(**data) data = data_model(**data)
return 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): 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: if len(filtered_series)==0:
return None return None
@ -299,7 +323,8 @@ class SQLHandler():
df_times = df_times.loc[~df_times[non_null_column].isnull()] # NOT null filter df_times = df_times.loc[~df_times[non_null_column].isnull()] # NOT null filter
# filter by the agency participant_type # filter by the agency participant_type
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 return times_agency
def filter_df_by_key_value(self, df, key, value)->pd.DataFrame: def filter_df_by_key_value(self, df, key, value)->pd.DataFrame:

View File

@ -866,18 +866,18 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
times_terminal = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TERMINAL.value) times_terminal = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TERMINAL.value)
# when one of the two values is null, the state is GREEN # 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() return self.get_no_violation_default_output()
# when one of the two values is null, the state is GREEN # 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() return self.get_no_violation_default_output()
# only incoming shipcalls matter. The other ones are not relevant for the pier_side selection # 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]: if shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]:
return self.get_no_violation_default_output() 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: if violation_state:
validation_name = "validation_rule_fct_agency_and_terminal_pier_side_disagreement" validation_name = "validation_rule_fct_agency_and_terminal_pier_side_disagreement"

View File

@ -33,6 +33,10 @@ class ValidationRules(ValidationRuleFunctions):
if len(df_times)==0: if len(df_times)==0:
return (StatusFlags.GREEN.value, []) 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 # filter by shipcall id
df_times = self.sql_handler.get_times_of_shipcall(shipcall) df_times = self.sql_handler.get_times_of_shipcall(shipcall)

View File

@ -1114,7 +1114,8 @@ def test_validation_rule_fct_agency_and_terminal_pier_side_disagreement__agency_
vr = build_sql_proxy_connection['vr'] vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple() shipcall = get_shipcall_simple()
df_times = get_df_times(shipcall) 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 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) (code, msg) = vr.validation_rule_fct_agency_and_terminal_pier_side_disagreement(shipcall=shipcall, df_times=df_times)
@ -1126,7 +1127,8 @@ def test_validation_rule_fct_agency_and_terminal_pier_side_disagreement__agency_
vr = build_sql_proxy_connection['vr'] vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple() shipcall = get_shipcall_simple()
df_times = get_df_times(shipcall) 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 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) (code, msg) = vr.validation_rule_fct_agency_and_terminal_pier_side_disagreement(shipcall=shipcall, df_times=df_times)
@ -1138,7 +1140,8 @@ def test_validation_rule_fct_agency_and_terminal_pier_side_disagreement__agency_
vr = build_sql_proxy_connection['vr'] vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple() shipcall = get_shipcall_simple()
df_times = get_df_times(shipcall) 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 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) (code, msg) = vr.validation_rule_fct_agency_and_terminal_pier_side_disagreement(shipcall=shipcall, df_times=df_times)
@ -1178,7 +1181,8 @@ def test_validation_rule_fct_agency_and_terminal_pier_side_agreement(build_sql_p
t2.participant_type = ParticipantType.TERMINAL.value t2.participant_type = ParticipantType.TERMINAL.value
# agreement # agreement
t1.pier_side = True shipcall.pier_side = True
# t1.pier_side = True
t2.pier_side = True t2.pier_side = True
time_objects = [t1, t2] time_objects = [t1, t2]
@ -1209,7 +1213,8 @@ def test_validation_rule_fct_agency_and_terminal_pier_side_disagreement(build_sq
t2.participant_type = ParticipantType.TERMINAL.value t2.participant_type = ParticipantType.TERMINAL.value
# disagreement # disagreement
t1.pier_side = True shipcall.pier_side = True
# t1.pier_side = True
t2.pier_side = False t2.pier_side = False
time_objects = [t1, t2] time_objects = [t1, t2]