From dc79f05b8b4a6c710792c4744463657cf69a67bd Mon Sep 17 00:00:00 2001 From: scopesorting Date: Thu, 30 Nov 2023 15:53:42 +0100 Subject: [PATCH] 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). --- src/server/BreCal/database/enums.py | 4 +- src/server/BreCal/database/sql_handler.py | 41 +++++++++++++++---- .../validators/validation_rule_functions.py | 6 +-- .../BreCal/validators/validation_rules.py | 4 ++ .../test_validation_rule_functions.py | 15 ++++--- 5 files changed, 52 insertions(+), 18 deletions(-) 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 e473a79..59497e3 100644 --- a/src/server/BreCal/database/sql_handler.py +++ b/src/server/BreCal/database/sql_handler.py @@ -1,6 +1,7 @@ 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 @@ -110,7 +111,8 @@ class SQLHandler(): self.initialize_shipcall_participant_list() # update the 'type' in shipcall_participants_map - self.add_participant_type_to_map() + # fully deprecated + # self.add_participant_type_to_map() return 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 respective data from the participants. Updates the shipcall_participant_map inplace. """ - spm = self.df_dict["shipcall_participant_map"] - participant_df = self.df_dict["participant"] + 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 + #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: @@ -159,7 +162,11 @@ class SQLHandler(): 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""" - 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 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) 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 @@ -299,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/validators/validation_rule_functions.py b/src/server/BreCal/validators/validation_rule_functions.py index 2769927..a7993cd 100644 --- a/src/server/BreCal/validators/validation_rule_functions.py +++ b/src/server/BreCal/validators/validation_rule_functions.py @@ -866,18 +866,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 b90f691..cabe5ec 100644 --- a/src/server/BreCal/validators/validation_rules.py +++ b/src/server/BreCal/validators/validation_rules.py @@ -33,6 +33,10 @@ class ValidationRules(ValidationRuleFunctions): 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) diff --git a/src/server/tests/validators/test_validation_rule_functions.py b/src/server/tests/validators/test_validation_rule_functions.py index ab85502..5c4e3a0 100644 --- a/src/server/tests/validators/test_validation_rule_functions.py +++ b/src/server/tests/validators/test_validation_rule_functions.py @@ -1114,7 +1114,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) @@ -1126,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"] = False (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'] 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) @@ -1178,7 +1181,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] @@ -1209,7 +1213,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]