implementing notifications, working on input validation. rebase.
This commit is contained in:
parent
1cc47d1eaf
commit
863d265669
@ -26,6 +26,8 @@ class ParticipantwiseTimeDelta():
|
|||||||
TUG = 960.0 # 16 h * 60 min/h = 960 min
|
TUG = 960.0 # 16 h * 60 min/h = 960 min
|
||||||
TERMINAL = 960.0 # 16 h * 60 min/h = 960 min
|
TERMINAL = 960.0 # 16 h * 60 min/h = 960 min
|
||||||
|
|
||||||
|
NOTIFICATION = 10.0 # after n minutes, an evaluation may rise a notification
|
||||||
|
|
||||||
class StatusFlags(Enum):
|
class StatusFlags(Enum):
|
||||||
"""
|
"""
|
||||||
these enumerators ensure that each traffic light validation rule state corresponds to a value, which will be used in the ValidationRules object to identify
|
these enumerators ensure that each traffic light validation rule state corresponds to a value, which will be used in the ValidationRules object to identify
|
||||||
@ -39,3 +41,10 @@ class StatusFlags(Enum):
|
|||||||
class PierSide(IntEnum):
|
class PierSide(IntEnum):
|
||||||
PORTSIDE = 0 # Port/Backbord
|
PORTSIDE = 0 # Port/Backbord
|
||||||
STARBOARD_SIDE = 1 # Starboard / Steuerbord
|
STARBOARD_SIDE = 1 # Starboard / Steuerbord
|
||||||
|
|
||||||
|
class NotificationType(IntFlag):
|
||||||
|
"""determines the method by which a notification is distributed to users. Flagging allows selecting multiple notification types."""
|
||||||
|
UNDEFINED = 0
|
||||||
|
EMAIL = 1
|
||||||
|
POPUP = 2
|
||||||
|
MESSENGER = 4
|
||||||
|
|||||||
0
src/server/BreCal/notifications/__init__.py
Normal file
0
src/server/BreCal/notifications/__init__.py
Normal file
115
src/server/BreCal/notifications/notification_functions.py
Normal file
115
src/server/BreCal/notifications/notification_functions.py
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import datetime
|
||||||
|
import pandas as pd
|
||||||
|
from BreCal.schemas.model import Notification
|
||||||
|
from BreCal.database.enums import NotificationType, ParticipantType, ShipcallType, StatusFlags
|
||||||
|
|
||||||
|
def create_notification(id, times_id, message, level, notification_type:NotificationType, created=None, modified=None):
|
||||||
|
created = (datetime.datetime.now()).isoformat() or created
|
||||||
|
|
||||||
|
notification = Notification(
|
||||||
|
id=id,
|
||||||
|
times_id=times_id, acknowledged=False, level=level, type=notification_type.value, message=message, created=created, modified=modified
|
||||||
|
)
|
||||||
|
return notification
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Verbosity Functions ####
|
||||||
|
|
||||||
|
def get_default_header()->str:
|
||||||
|
# HEADER (greeting and default message)
|
||||||
|
header = "Dear Sir or Madam\n\nThank you for participating in the project 'Bremen Calling'. During analysis, our software has identified an event, which may be worth a second look. Here is the summary. \n\n"
|
||||||
|
return header
|
||||||
|
|
||||||
|
def get_default_footer()->str:
|
||||||
|
# FOOTER (signature)
|
||||||
|
footer = "\n\nWe would kindly ask you to have a look at the shipcall and verify, if any action is required from your side. \n\nKind regards\nThe 'Bremen Calling' Team"
|
||||||
|
return footer
|
||||||
|
|
||||||
|
def get_agency_name(sql_handler, times_df):
|
||||||
|
times_agency = times_df.loc[times_df["participant_type"]==ParticipantType.AGENCY.value,"participant_id"]
|
||||||
|
if len(times_agency)==0:
|
||||||
|
agency_name = ""
|
||||||
|
else:
|
||||||
|
agency_participant_id = times_agency.iloc[0]
|
||||||
|
agency_name = sql_handler.df_dict.get("participant").loc[agency_participant_id,"name"]
|
||||||
|
return agency_name
|
||||||
|
|
||||||
|
def get_ship_name(sql_handler, shipcall):
|
||||||
|
ship = sql_handler.df_dict.get("ship").loc[shipcall.ship_id]
|
||||||
|
ship_name = ship.loc["name"] # when calling ship.name, the ID is returned (pandas syntax)
|
||||||
|
return ship_name
|
||||||
|
|
||||||
|
|
||||||
|
def create_notification_body(sql_handler, times_df, shipcall, result)->str:
|
||||||
|
# #TODO: add 'Link zum Anlauf'
|
||||||
|
# URL: https://trello.com/c/qenZyJxR/75-als-bsmd-m%C3%B6chte-ich-%C3%BCber-gelbe-und-rote-ampeln-informiert-werden-um-die-systembeteiligung-zu-st%C3%A4rken
|
||||||
|
header = get_default_header()
|
||||||
|
footer = get_default_footer()
|
||||||
|
|
||||||
|
agency_name = get_agency_name(sql_handler, times_df)
|
||||||
|
ship_name = get_ship_name(sql_handler, shipcall)
|
||||||
|
|
||||||
|
verbosity_introduction = f"Respective Shipcall:\n"
|
||||||
|
traffic_state_verbosity = f"\tTraffic Light State: {StatusFlags(result[0]).name}\n"
|
||||||
|
ship_name_verbosity = f"\tShip: {ship_name} (the ship is {ShipcallType(shipcall.type).name.lower()})\n"
|
||||||
|
agency_name_verbosity = f"\tResponsible Agency: {agency_name}\n"
|
||||||
|
eta_verbosity = f"\tEstimated Arrival Time: {shipcall.eta.isoformat()}\n" if not pd.isna(shipcall.eta) else ""
|
||||||
|
etd_verbosity = f"\tEstimated Departure Time: {shipcall.etd.isoformat()}\n" if not pd.isna(shipcall.etd) else ""
|
||||||
|
error_verbosity = f"\nError Description:\n\t" + "\n\t".join(result[1])
|
||||||
|
|
||||||
|
message_body = "".join([header, verbosity_introduction, traffic_state_verbosity, ship_name_verbosity, agency_name_verbosity, eta_verbosity, etd_verbosity, error_verbosity, footer])
|
||||||
|
return message_body
|
||||||
|
|
||||||
|
|
||||||
|
class Notifier():
|
||||||
|
"""An object that helps with the logic of selecting eligible shipcalls to create the correct notifications for the respective users."""
|
||||||
|
def __init__(self)->None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def determine_notification_state(self, state_old, state_new):
|
||||||
|
"""
|
||||||
|
this method determines state changes in the notification state. When the state increases, a user is notified about it.
|
||||||
|
state order: (NONE = GREEN < YELLOW < RED)
|
||||||
|
"""
|
||||||
|
# identify a state increase
|
||||||
|
should_notify = self.identify_notification_state_change(state_old=state_old, state_new=state_new)
|
||||||
|
|
||||||
|
# when a state increases, a notification must be sent. Thereby, the field should be set to False ({evaluation_notifications_sent})
|
||||||
|
evaluation_notifications_sent = False if bool(should_notify) else None
|
||||||
|
return evaluation_notifications_sent
|
||||||
|
|
||||||
|
def identify_notification_state_change(self, state_old, state_new) -> bool:
|
||||||
|
"""
|
||||||
|
determines, whether the observed state change should trigger a notification.
|
||||||
|
internally, this function maps StatusFlags to an integer and determines, if the successor state is more severe than the predecessor.
|
||||||
|
|
||||||
|
state changes trigger a notification in the following cases:
|
||||||
|
green -> yellow
|
||||||
|
green -> red
|
||||||
|
yellow -> red
|
||||||
|
|
||||||
|
(none -> yellow) or (none -> red)
|
||||||
|
due to the values in the enumeration objects, the states are mapped to provide this function.
|
||||||
|
green=1, yellow=2, red=3, none=1. Hence, critical changes can be observed by simply checking with "greater than".
|
||||||
|
|
||||||
|
returns bool, whether a notification should be triggered
|
||||||
|
"""
|
||||||
|
# state_old is always considered at least 'Green' (1)
|
||||||
|
if state_old is None:
|
||||||
|
state_old = StatusFlags.NONE.value
|
||||||
|
state_old = max(int(state_old), StatusFlags.GREEN.value)
|
||||||
|
return int(state_new) > int(state_old)
|
||||||
|
|
||||||
|
def get_notification_times(self, evaluation_states_new)->list[datetime.datetime]:
|
||||||
|
"""# build the list of evaluation times ('now', as isoformat)"""
|
||||||
|
evaluation_times = [datetime.datetime.now().isoformat() for _i in range(len(evaluation_states_new))]
|
||||||
|
return evaluation_times
|
||||||
|
|
||||||
|
def get_notification_states(self, evaluation_states_old, evaluation_states_new)->list[bool]:
|
||||||
|
"""# build the list of 'evaluation_notifications_sent'. The value is 'False', when a notification should be created"""
|
||||||
|
evaluation_notifications_sent = [self.notifier.determine_notification_state(state_old=int(state_old), state_new=int(state_new)) for state_old, state_new in zip(evaluation_states_old, evaluation_states_new)]
|
||||||
|
return evaluation_notifications_sent
|
||||||
|
|
||||||
@ -184,11 +184,18 @@ class ShipcallSchema(Schema):
|
|||||||
anchored = fields.Bool(Required = False, allow_none=True)
|
anchored = fields.Bool(Required = False, allow_none=True)
|
||||||
moored_lock = fields.Bool(Required = False, allow_none=True)
|
moored_lock = fields.Bool(Required = False, allow_none=True)
|
||||||
canceled = fields.Bool(Required = False, allow_none=True)
|
canceled = fields.Bool(Required = False, allow_none=True)
|
||||||
|
<<<<<<< HEAD
|
||||||
evaluation = fields.Enum(EvaluationType, required=False, allow_none=True, default=EvaluationType.undefined)
|
evaluation = fields.Enum(EvaluationType, required=False, allow_none=True, default=EvaluationType.undefined)
|
||||||
evaluation_message = fields.Str(allow_none=True, metadata={'Required':False}) # Solving: RemovedInMarshmallow4Warning: Passing field metadata as keyword arguments is deprecated. Use the explicit `metadata=...` argument instead. Additional metadata: {'Required': False}
|
evaluation_message = fields.Str(allow_none=True, metadata={'Required':False}) # Solving: RemovedInMarshmallow4Warning: Passing field metadata as keyword arguments is deprecated. Use the explicit `metadata=...` argument instead. Additional metadata: {'Required': False}
|
||||||
evaluation_time = fields.DateTime(Required = False, allow_none=True)
|
evaluation_time = fields.DateTime(Required = False, allow_none=True)
|
||||||
evaluation_notifications_sent = fields.Bool(Required = False, allow_none=True)
|
evaluation_notifications_sent = fields.Bool(Required = False, allow_none=True)
|
||||||
time_ref_point = fields.Int(Required = False, allow_none=True)
|
time_ref_point = fields.Int(Required = False, allow_none=True)
|
||||||
|
=======
|
||||||
|
evaluation = fields.Integer(Required = False, allow_none=True)
|
||||||
|
evaluation_message = fields.String(allow_none=True, metadata={'Required':False}) # Solving: RemovedInMarshmallow4Warning: Passing field metadata as keyword arguments is deprecated. Use the explicit `metadata=...` argument instead. Additional metadata: {'Required': False}
|
||||||
|
evaluation_time = fields.DateTime(Required = False, allow_none=True)
|
||||||
|
evaluation_notifications_sent = fields.Bool(Required = False, allow_none=True)
|
||||||
|
>>>>>>> a5284a4 (implementing notifications, working on input validation)
|
||||||
participants = fields.List(fields.Nested(ParticipantAssignmentSchema))
|
participants = fields.List(fields.Nested(ParticipantAssignmentSchema))
|
||||||
created = fields.DateTime(Required = False, allow_none=True)
|
created = fields.DateTime(Required = False, allow_none=True)
|
||||||
modified = fields.DateTime(Required = False, allow_none=True)
|
modified = fields.DateTime(Required = False, allow_none=True)
|
||||||
@ -251,6 +258,8 @@ class Shipcall:
|
|||||||
created: datetime
|
created: datetime
|
||||||
modified: datetime
|
modified: datetime
|
||||||
participants: List[Participant_Assignment] = field(default_factory=list)
|
participants: List[Participant_Assignment] = field(default_factory=list)
|
||||||
|
evaluation_time : datetime = None
|
||||||
|
evaluation_notifications_sent : bool = None
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -57,6 +57,11 @@ def add_function_to_schedule__update_shipcalls(interval_in_minutes:int, options:
|
|||||||
schedule.every(interval_in_minutes).minutes.do(UpdateShipcalls, **kwargs_)
|
schedule.every(interval_in_minutes).minutes.do(UpdateShipcalls, **kwargs_)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def add_function_to_schedule__send_notifications(vr, interval_in_minutes:int=10):
|
||||||
|
schedule.every(interval_in_minutes).minutes.do(vr.notifier.send_notifications)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
def setup_schedule(update_shipcalls_interval_in_minutes:int=60):
|
def setup_schedule(update_shipcalls_interval_in_minutes:int=60):
|
||||||
|
|
||||||
logging.getLogger('schedule').setLevel(logging.INFO); # set the logging level of the schedule module to INFO
|
logging.getLogger('schedule').setLevel(logging.INFO); # set the logging level of the schedule module to INFO
|
||||||
|
|||||||
@ -37,12 +37,12 @@ def get_shipcall_simple():
|
|||||||
recommended_tugs = 2 # assert 0<recommended_tugs<={threshold}. E.g., 20 should not be exceeded.
|
recommended_tugs = 2 # assert 0<recommended_tugs<={threshold}. E.g., 20 should not be exceeded.
|
||||||
anchored = False
|
anchored = False
|
||||||
moored_lock = False # de: 'Festmacherschleuse', en: 'moored lock'
|
moored_lock = False # de: 'Festmacherschleuse', en: 'moored lock'
|
||||||
|
|
||||||
canceled = False
|
canceled = False
|
||||||
|
|
||||||
evaluation = None
|
evaluation = None
|
||||||
evaluation_message = ""
|
evaluation_message = ""
|
||||||
evaluation_time = None
|
evaluation_time = None
|
||||||
evaluation_notifications_sent = False
|
evaluation_notifications_sent = None
|
||||||
created = datetime.datetime.now()
|
created = datetime.datetime.now()
|
||||||
modified = created+datetime.timedelta(seconds=10)
|
modified = created+datetime.timedelta(seconds=10)
|
||||||
|
|
||||||
|
|||||||
@ -3,9 +3,11 @@ import logging
|
|||||||
import re
|
import re
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
import datetime
|
||||||
from BreCal.database.enums import StatusFlags
|
from BreCal.database.enums import StatusFlags
|
||||||
from BreCal.validators.validation_rule_functions import ValidationRuleFunctions
|
from BreCal.validators.validation_rule_functions import ValidationRuleFunctions
|
||||||
from BreCal.schemas.model import Shipcall
|
from BreCal.schemas.model import Shipcall
|
||||||
|
from BreCal.notifications.notification_functions import Notifier
|
||||||
|
|
||||||
|
|
||||||
class ValidationRules(ValidationRuleFunctions):
|
class ValidationRules(ValidationRuleFunctions):
|
||||||
@ -17,10 +19,7 @@ class ValidationRules(ValidationRuleFunctions):
|
|||||||
"""
|
"""
|
||||||
def __init__(self, sql_handler): # use the entire data that is provided for this query (e.g., json input)
|
def __init__(self, sql_handler): # use the entire data that is provided for this query (e.g., json input)
|
||||||
super().__init__(sql_handler)
|
super().__init__(sql_handler)
|
||||||
|
self.notifier = Notifier()
|
||||||
self.validation_state = self.determine_validation_state()
|
|
||||||
# currently flagged: notification_state initially was based on using one ValidationRules object for each query. This is deprecated.
|
|
||||||
# self.notification_state = self.determine_notification_state() # (state:str, should_notify:bool)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def evaluate(self, shipcall):
|
def evaluate(self, shipcall):
|
||||||
@ -74,16 +73,25 @@ class ValidationRules(ValidationRuleFunctions):
|
|||||||
return evaluation_state, violations
|
return evaluation_state, violations
|
||||||
|
|
||||||
def evaluate_shipcalls(self, shipcall_df:pd.DataFrame)->pd.DataFrame:
|
def evaluate_shipcalls(self, shipcall_df:pd.DataFrame)->pd.DataFrame:
|
||||||
"""apply 'evaluate_shipcall_from_df' to each individual shipcall in {shipcall_df}. Returns shipcall_df ('evaluation' and 'evaluation_message' are updated)"""
|
"""apply 'evaluate_shipcall_from_df' to each individual shipcall in {shipcall_df}. Returns shipcall_df ('evaluation', 'evaluation_message', 'evaluation_time' and 'evaluation_notifications_sent' are updated)"""
|
||||||
results = shipcall_df.apply(lambda x: self.evaluate_shipcall_from_df(x), axis=1).values
|
evaluation_states_old = [state_old for state_old in shipcall_df.loc[:,"evaluation"]]
|
||||||
|
results = shipcall_df.apply(lambda x: self.evaluate_shipcall_from_df(x), axis=1).values # returns tuple (state, message)
|
||||||
|
|
||||||
# unbundle individual results. evaluation_state becomes an integer, violation
|
# unbundle individual results. evaluation_states becomes an integer, violation
|
||||||
evaluation_state = [StatusFlags(res[0]).value for res in results]
|
evaluation_states_new = [StatusFlags(res[0]).value for res in results]
|
||||||
violations = [",\r\n".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]
|
violations = [self.concise_evaluation_message_if_too_long(violation) for violation in violations]
|
||||||
|
|
||||||
shipcall_df.loc[:,"evaluation"] = evaluation_state
|
# build the list of evaluation times ('now', as isoformat)
|
||||||
|
evaluation_times = self.notifier.get_notification_times(evaluation_states_new)
|
||||||
|
|
||||||
|
# build the list of 'evaluation_notifications_sent'. The value is 'False', when a notification should be created
|
||||||
|
evaluation_notifications_sent = self.get_notification_states(evaluation_states_old, evaluation_states_new)
|
||||||
|
|
||||||
|
shipcall_df.loc[:,"evaluation"] = evaluation_states_new
|
||||||
shipcall_df.loc[:,"evaluation_message"] = violations
|
shipcall_df.loc[:,"evaluation_message"] = violations
|
||||||
|
shipcall_df.loc[:,"evaluation_times"] = evaluation_times
|
||||||
|
shipcall_df.loc[:,"evaluation_notifications_sent"] = evaluation_notifications_sent
|
||||||
return shipcall_df
|
return shipcall_df
|
||||||
|
|
||||||
def concise_evaluation_message_if_too_long(self, violation):
|
def concise_evaluation_message_if_too_long(self, violation):
|
||||||
@ -100,53 +108,31 @@ class ValidationRules(ValidationRuleFunctions):
|
|||||||
# e.g.: Evaluation message too long. Violated Rules: ['Rule #0001C', 'Rule #0001H', 'Rule #0001F', 'Rule #0001G', 'Rule #0001L', 'Rule #0001M', 'Rule #0001J', 'Rule #0001K']
|
# 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}"
|
violation = f"Evaluation message too long. Violated Rules: {concise}"
|
||||||
return violation
|
return violation
|
||||||
|
|
||||||
def determine_validation_state(self) -> str:
|
|
||||||
"""
|
|
||||||
this method determines the validation state of a shipcall. The state is either ['green', 'yellow', 'red'] and signals,
|
|
||||||
whether an entry causes issues within the workflow of users.
|
|
||||||
|
|
||||||
returns: validation_state_new (str)
|
|
||||||
"""
|
|
||||||
(validation_state_new, description) = self.undefined_method()
|
|
||||||
# should there also be notifications for critical validation states? In principle, the traffic light itself provides that notification.
|
|
||||||
self.validation_state = validation_state_new
|
|
||||||
return validation_state_new
|
|
||||||
|
|
||||||
def determine_notification_state(self) -> (str, bool):
|
|
||||||
"""
|
|
||||||
this method determines state changes in the notification state. When the state is changed to yellow or red,
|
|
||||||
a user is notified about it. The only exception for this rule is when the state was yellow or red before,
|
|
||||||
as the user has then already been notified.
|
|
||||||
|
|
||||||
returns: notification_state_new (str), should_notify (bool)
|
|
||||||
"""
|
|
||||||
(state_new, description) = self.undefined_method() # determine the successor
|
|
||||||
should_notify = self.identify_notification_state_change(state_new)
|
|
||||||
self.notification_state = state_new # overwrite the predecessor
|
|
||||||
return state_new, should_notify
|
|
||||||
|
|
||||||
def identify_notification_state_change(self, state_new) -> bool:
|
|
||||||
"""
|
|
||||||
determines, whether the observed state change should trigger a notification.
|
|
||||||
internally, this function maps a color string to an integer and determines, if the successor state is more severe than the predecessor.
|
|
||||||
|
|
||||||
state changes trigger a notification in the following cases:
|
|
||||||
green -> yellow
|
|
||||||
green -> red
|
|
||||||
yellow -> red
|
|
||||||
|
|
||||||
(none -> yellow) or (none -> red)
|
|
||||||
due to the values in the enumeration objects, the states are mapped to provide this function.
|
|
||||||
green=1, yellow=2, red=3, none=1. Hence, critical changes can be observed by simply checking with "greater than".
|
|
||||||
|
|
||||||
returns bool, whether a notification should be triggered
|
|
||||||
"""
|
|
||||||
# state_old is always considered at least 'Green' (1)
|
|
||||||
state_old = max(copy.copy(self.notification_state) if "notification_state" in list(self.__dict__.keys()) else StatusFlags.NONE, StatusFlags.GREEN.value)
|
|
||||||
return state_new.value > state_old.value
|
|
||||||
|
|
||||||
def undefined_method(self) -> str:
|
def undefined_method(self) -> str:
|
||||||
"""this function should apply the ValidationRules to the respective .shipcall, in regards to .times"""
|
"""this function should apply the ValidationRules to the respective .shipcall, in regards to .times"""
|
||||||
# #TODO_traffic_state
|
|
||||||
return (StatusFlags.GREEN, False) # (state:str, should_notify:bool)
|
return (StatusFlags.GREEN, False) # (state:str, should_notify:bool)
|
||||||
|
|
||||||
|
|
||||||
|
def inspect_shipcall_evaluation(vr, sql_handler, shipcall_id):
|
||||||
|
"""
|
||||||
|
# debug only!
|
||||||
|
|
||||||
|
a simple debugging function, which serves in inspecting an evaluation function for a single shipcall id. It returns the result and all related data.
|
||||||
|
returns: result, shipcall_df (filtered by shipcall id), shipcall, spm (shipcall participant map, filtered by shipcall id), times_df (filtered by shipcall id)
|
||||||
|
"""
|
||||||
|
shipcall_df = sql_handler.df_dict.get("shipcall").loc[shipcall_id:shipcall_id,:]
|
||||||
|
|
||||||
|
shipcall = Shipcall(**{**{"id":shipcall_id},**sql_handler.df_dict.get("shipcall").loc[shipcall_id].to_dict()})
|
||||||
|
result = vr.evaluate(shipcall=shipcall)
|
||||||
|
notification_state = vr.identify_notification_state_change(state_old=int(shipcall.evaluation), state_new=int(result[0]))
|
||||||
|
print(f"Previous state: {int(shipcall.evaluation)}, New State: {result[0]}, Notification State: {notification_state}")
|
||||||
|
|
||||||
|
times_df = sql_handler.df_dict.get("times")
|
||||||
|
times_df = times_df.loc[times_df["shipcall_id"]==shipcall_id]
|
||||||
|
|
||||||
|
|
||||||
|
spm = sql_handler.df_dict["shipcall_participant_map"]
|
||||||
|
spm = spm.loc[spm["shipcall_id"]==shipcall_id]
|
||||||
|
|
||||||
|
return result, shipcall_df, shipcall, spm, times_df
|
||||||
|
|||||||
Reference in New Issue
Block a user