fixed some smaller issues

This commit is contained in:
Daniel Schick 2024-12-17 14:51:04 +01:00
parent 47da3ff475
commit 622ab6b4a3
2 changed files with 90 additions and 31 deletions

View File

@ -81,7 +81,7 @@ def SendEmails(email_dict):
commands = pydapper.using(pooledConnection) commands = pydapper.using(pooledConnection)
conn = smtplib.SMTP(defs.email_credentials["server"], defs.email_credentials["port"]) conn = smtplib.SMTP(defs.email_credentials["server"], defs.email_credentials["port"])
conn.set_debuglevel(1) conn.set_debuglevel(1) # set this to 0 to disable debug output to log
conn.ehlo() conn.ehlo()
conn.starttls() conn.starttls()
conn.ehlo() conn.ehlo()
@ -107,7 +107,10 @@ def SendEmails(email_dict):
for notification in notifications: for notification in notifications:
message_type = defs.message_types[notification.type] message_type = next((x for x in defs.message_types if x["type"] == notification.type), None)
if message_type is None:
logging.error(f"Message type {notification.type} not found")
continue
with open(os.path.join(current_path,'../msg/notification_element.html'), mode="r", encoding="utf-8") as file: with open(os.path.join(current_path,'../msg/notification_element.html'), mode="r", encoding="utf-8") as file:
element = file.read() element = file.read()
@ -185,21 +188,38 @@ def SendNotifications():
for notification in data: for notification in data:
if notification.participant_id not in users_dict: if not notification.participant_id: # no participant defined, this update goes to all participants of this shipcall
continue p_query = "SELECT * from shipcall_participant_map where shipcall_id = ?id?"
users = users_dict[notification.participant_id] assigned_participants = commands.query(p_query, model=model.ShipcallParticipantMap, param={"id":notification.shipcall_id})
for user in users: for assigned_participant in assigned_participants:
# send notification to user users = users_dict[assigned_participant.participant_id]
if user.notify_email: for user in users:
if user not in email_dict: # send notification to user
email_dict[user] = [] if user.notify_email:
email_dict[user].append(notification) if user not in email_dict:
if user.notify_whatsapp: email_dict[user] = []
# TBD email_dict[user].append(notification)
pass if user.notify_whatsapp:
if user.notify_signal: # TBD
# TBD pass
pass if user.notify_signal:
# TBD
pass
else:
users = users_dict[notification.participant_id]
for user in users:
# send notification to user
if user.notify_email:
if user not in email_dict:
email_dict[user] = []
email_dict[user].append(notification)
if user.notify_whatsapp:
# TBD
pass
if user.notify_signal:
# TBD
pass
# mark as sent # mark as sent
commands.execute("UPDATE notification SET level = 2 WHERE id = ?id?", param={"id":notification.id}) commands.execute("UPDATE notification SET level = 2 WHERE id = ?id?", param={"id":notification.id})
@ -273,11 +293,8 @@ def setup_schedule(update_shipcalls_interval_in_minutes:int=60):
schedule.every().day.at("09:00").do(eval_next_24_hrs) schedule.every().day.at("09:00").do(eval_next_24_hrs)
SendNotifications()
add_function_to_schedule_send_notifications(1) add_function_to_schedule_send_notifications(1)
# TODO: Add schedule function to evaluate all notifications in level 1 and create actions
return return

View File

@ -1,5 +1,6 @@
import copy import copy
import logging import logging
import pydapper
import re import re
import numpy as np import numpy as np
import pandas as pd import pandas as pd
@ -7,6 +8,7 @@ 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.local_db import getPoolConnection
class ValidationRules(ValidationRuleFunctions): class ValidationRules(ValidationRuleFunctions):
@ -76,7 +78,7 @@ class ValidationRules(ValidationRuleFunctions):
evaluation_states_old = [state_old if not pd.isna(state_old) else 0 for state_old in evaluation_states_old] evaluation_states_old = [state_old if not pd.isna(state_old) else 0 for state_old in evaluation_states_old]
results = shipcall_df.apply(lambda x: self.evaluate_shipcall_from_df(x), axis=1).values # returns tuple (state, message) results = shipcall_df.apply(lambda x: self.evaluate_shipcall_from_df(x), axis=1).values # returns tuple (state, message)
# unbundle individual results. evaluation_states becomes an integer, violation # unbundle individual results. evaluation_states becomes an integer, violation
evaluation_states_new = [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]
@ -84,9 +86,49 @@ class ValidationRules(ValidationRuleFunctions):
# build the list of evaluation times ('now', as isoformat) # build the list of evaluation times ('now', as isoformat)
#evaluation_time = self.get_notification_times(evaluation_states_new) #evaluation_time = self.get_notification_times(evaluation_states_new)
send_notification = False
if evaluation_states_old is not None and evaluation_states_new is not None:
if len(evaluation_states_old) == 1 and len(evaluation_states_new) == 1:
if evaluation_states_old[0] != evaluation_states_new[0]:
pooledConnection = getPoolConnection()
commands = pydapper.using(pooledConnection)
if evaluation_states_new[0] == 2:
match evaluation_states_old[0]:
case 0:
send_notification = True
case 1:
send_notification = True
if evaluation_states_new[0] == 3:
match evaluation_states_old[0]:
case 0:
send_notification = True
case 1:
send_notification = True
case 2:
send_notification = True
if send_notification:
query = "INSERT INTO notification (shipcall_id, type, level, message) VALUES (?shipcall_id?, 3, 0, ?message?)"
commands.execute(query, param={"shipcall_id" : int(shipcall_df.index[0]), "message" : violations[0]})
if evaluation_states_new[0] == 1 and evaluation_states_old[0] != 0: # this resolves the conflict
query = "SELECT * from notification where shipcall_id = ?shipcall_id? and type = 3 and level = 0"
existing_notification = commands.query(query, param={"shipcall_id" : int(shipcall_df.index[0])})
if len(existing_notification) > 0:
query = "DELETE from notification where id = ?id?"
commands.execute(query, param={"id" : existing_notification[0]["id"]})
else:
query = "INSERT INTO notification (shipcall_id, type, level) VALUES (?shipcall_id?, 4, 0)"
commands.execute(query, param={"shipcall_id" : int(shipcall_df.index[0])})
pooledConnection.close()
# build the list of 'evaluation_notifications_sent'. The value is 'False', when a notification should be created # 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) #evaluation_notifications_sent = self.get_notification_states(evaluation_states_old, evaluation_states_new)
# TODO: detect evaluation state changes and create notifications
shipcall_df.loc[:,"evaluation"] = 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_time"] = evaluation_time #shipcall_df.loc[:,"evaluation_time"] = evaluation_time
@ -107,14 +149,14 @@ 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 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"""
return (StatusFlags.GREEN, False) # (state:str, should_notify:bool) return (StatusFlags.GREEN, False) # (state:str, should_notify:bool)
def determine_notification_state(self, state_old, state_new): 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. 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) state order: (NONE = GREEN < YELLOW < RED)
""" """
# identify a state increase # identify a state increase
@ -123,10 +165,10 @@ class ValidationRules(ValidationRuleFunctions):
# when a state increases, a notification must be sent. Thereby, the field should be set to False ({evaluation_notifications_sent}) # 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 evaluation_notifications_sent = False if bool(should_notify) else None
return evaluation_notifications_sent return evaluation_notifications_sent
def identify_notification_state_change(self, state_old, state_new) -> bool: def identify_notification_state_change(self, state_old, state_new) -> bool:
""" """
determines, whether the observed state change should trigger a notification. 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. 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: state changes trigger a notification in the following cases:
@ -136,7 +178,7 @@ class ValidationRules(ValidationRuleFunctions):
(none -> yellow) or (none -> red) (none -> yellow) or (none -> red)
due to the values in the enumeration objects, the states are mapped to provide this function. 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". 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 returns bool, whether a notification should be triggered
""" """
@ -145,12 +187,12 @@ class ValidationRules(ValidationRuleFunctions):
state_old = StatusFlags.NONE.value state_old = StatusFlags.NONE.value
state_old = max(int(state_old), StatusFlags.GREEN.value) state_old = max(int(state_old), StatusFlags.GREEN.value)
return int(state_new) > int(state_old) return int(state_new) > int(state_old)
def get_notification_times(self, evaluation_states_new)->list[datetime.datetime]: def get_notification_times(self, evaluation_states_new)->list[datetime.datetime]:
"""# build the list of evaluation times ('now', as isoformat)""" """# build the list of evaluation times ('now', as isoformat)"""
evaluation_times = [datetime.datetime.now().isoformat() for _i in range(len(evaluation_states_new))] evaluation_times = [datetime.datetime.now().isoformat() for _i in range(len(evaluation_states_new))]
return evaluation_times return evaluation_times
def get_notification_states(self, evaluation_states_old, evaluation_states_new)->list[bool]: 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""" """# build the list of 'evaluation_notifications_sent'. The value is 'False', when a notification should be created"""
evaluation_notifications_sent = [self.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)] evaluation_notifications_sent = [self.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)]
@ -160,7 +202,7 @@ class ValidationRules(ValidationRuleFunctions):
def inspect_shipcall_evaluation(vr, sql_handler, shipcall_id): def inspect_shipcall_evaluation(vr, sql_handler, shipcall_id):
""" """
# debug only! # 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. 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) returns: result, shipcall_df (filtered by shipcall id), shipcall, spm (shipcall participant map, filtered by shipcall id), times_df (filtered by shipcall id)
""" """
@ -170,7 +212,7 @@ def inspect_shipcall_evaluation(vr, sql_handler, shipcall_id):
result = vr.evaluate(shipcall=shipcall) result = vr.evaluate(shipcall=shipcall)
notification_state = vr.identify_notification_state_change(state_old=int(shipcall.evaluation), state_new=int(result[0])) 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}") 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 = sql_handler.df_dict.get("times")
times_df = times_df.loc[times_df["shipcall_id"]==shipcall_id] times_df = times_df.loc[times_df["shipcall_id"]==shipcall_id]