Need help with adding a scheduler app to my Dash Plotly app

Hello everyone,

I have developed a Dash Plotly app that visualizes data from my database. Now, I want to add a scheduler app to my existing Dash app that can run certain background tasks periodically, such as fetching data, sending email alerts, or performing calculations.

I have found some example code for the scheduler app and I understand how it works, but I’m not sure where exactly to put it in my Dash app and how to run it effectively.

Here is the code for the scheduler app:

import time
import schedule
import win32com

database = constants.database
# Function to send email
def send_email(subject, message, to):
    outlook = win32com.client.Dispatch('Outlook.Application')
    mail = outlook.CreateItem(0)
    mail.Subject = subject
    mail.To = to
    mail.Body = message
    mail.Send()

# Function to fetch the downtime
def fetch_the_downtime():
    start_time = datetime.datetime.now() - datetime.timedelta(days=1)
    end_time = datetime.datetime.now()
    parameters = [start_time, end_time]
    for db in database:
        query_string = mssql_queries.build_db_query_app7()
        records = mssql_conn.execute_query(query_string, parameters, str(db))
        if records:
            dataframe = pd.DataFrame.from_records(records)
            dataframe.columns = ["ID", "Timestamp", "Duration", "Week Number", "FailureCode", "FailureDescription"]
            dataframe["Duration"] = (
                dataframe["Duration"].astype("datetime64[s]").dt.strftime("%H:%M:%S")
            )
            # Check if any downtime duration is over 3600 seconds
            if (dataframe['Duration'] > '01:00:00').any():
                send_email('Downtime Alert', f"{db} - Some downtime duration is over 3600 seconds.", 'some mail')

# Refresh the function every 30 minutes
        while True:
            fetch_the_downtime()
            time.sleep(1800)  # 1800 seconds = 30 minutes
#Function to check scrap rate
def check_scrap_rate():
    # Connect to the databases
    conn_dict = {}
    for db in database:
        
        while True:
            # Calculate the time window for scrap rate calculation
            now = datetime.datetime.now()
            one_hour_ago = now - datetime.timedelta(hours=1)
            # Check the scrap rate for each database
            for db in database:
                # Execute the database query to get the scrap rate
                query = f'''
                    SELECT COUNT(*) AS total_items, SUM(CASE WHEN [Status] = 3 THEN 1 ELSE 0 END) AS scrap_items
                    FROM [{db}].[dbo].[ItemLog]
                    WHERE [Type] = 1 AND [Timestamp] BETWEEN '{one_hour_ago}' AND '{now}'
                '''
                cursor = conn_dict[db].cursor()
                cursor.execute(query)
                result = cursor.fetchone()
                # Calculate the scrap rate
                if result:
                    total_items = result[0]
                    scrap_items = result[1]
                    if total_items > 0:
                        scrap_rate = 100 * scrap_items / total_items
                    else:
                        scrap_rate = 0
                else:
                    scrap_rate = 0
                # Check if the scrap rate of the last hour is higher than 3% or the scrap rate
                # in the last 500 pieces produced is higher than 3%
                if scrap_rate > 3:
                    subject = f"{db} more than 3% scrap"
                    message = f"Scrap rate of last hour is {scrap_rate:.2f}%, which is higher than 3%."
                    send_email(subject, message, 'some mail')
                else:
                    # Execute the database query to get the number of items produced in the last 500 pieces
                    query = f'''
                        SELECT COUNT(*) AS produced_items, SUM(CASE WHEN [Status] = 3 THEN 1 ELSE 0 END) AS scrap_items
                        FROM [{db}].[dbo].[ItemLog]
                        WHERE [Type] = 1 ORDER BY [Timestamp] DESC LIMIT 500
                    '''
                    cursor = conn_dict[db].cursor()
                    cursor.execute(query)
                    result = cursor.fetchone()
                    # Calculate the scrap rate in the last 500 pieces
                    if result:
                        produced_items = result[0]
                        scrap_items = result[1]
                        if produced_items > 0:
                            scrap_rate = 100 * scrap_items / produced_items
                        else:
                            scrap_rate = 0
                    else:
                        scrap_rate = 0
                    # Check if the scrap rate in the last 500 pieces is higher than 3%
                    if scrap_rate > 3:
                        subject = f"{db} more than 3% scrap in last 500 pieces"
                        message = f"Scrap rate in last 500 pieces is {scrap_rate:.2f}%, which is higher than 3%."
                        send_email(subject, message, 'some mail')
        # Wait for 30 minutes before checking
                time.sleep(1800)

    # Schedule the job to run every day at 8 AM
schedule.every().day.at("08:00").do(check_scrap_rate)

    # Loop to run the scheduled jobs
# while True:
#     schedule.run_pending()
#     time.sleep(1)

In my Dash app, I have an index.py file where I define the layout and callbacks for my app. Should I add the scheduler code in this file? Or should I create a separate file for the scheduler and somehow run it alongside my Dash app?

index.py

from dash import dcc
from dash import html
from dash.dependencies import Input, Output
from app import app
from apps import app1, app2, app3, app4, app5, app6, app7, app8, scheduler
import nav

app.layout = html.Div(
    [dcc.Location(id="url", refresh=False), html.Div(id="page-content")]
)

# "complete" layout
app.validation_layout = html.Div(
    [
        app1.layout,
        app2.layout,
        app3.layout,
        app4.layout,
        app5.layout,
        app6.layout,
        app7.layout,
        app8.layout,
        nav.layout,
        app.layout,
    ]
)


@app.callback(Output("page-content", "children"), [Input("url", "pathname")])
def display_page(pathname):
    if pathname == "/":
        return nav.layout
    elif pathname == "/apps/Downtime_and_Reasons":
        # app.title = "Downtime and Reasons"
        return app1.layout
    elif pathname == "/apps/Production_history":
        # app.title = "Report of Check Criteria"
        return app2.layout
    elif pathname == "/apps/Quantity_and_Scrap":
        # app.title = "Quantity and Scrap"
        return app3.layout
    elif pathname == "/apps/User_Log":
        # app.title = "User Log"
        return app4.layout
    elif pathname == "/apps/Error_Log":
        # app.title = "Error Log"
        return app5.layout
    elif pathname == "/apps/Error_Log":
        # app.title = "Error Log"
        return app5.layout
    elif pathname == "/apps/Parameter_Log":
        # app.title = "Parameter Log"
        return app6.layout
    elif pathname == "/apps/Downtime_Log":
        # app.title = "Downtime Log"
        return app7.layout
    elif pathname == "/apps/Overall_Equipment_Effectiveness":
        # app.title = "Overall Equipment Effectiveness"
        return app8.layout
    else:
        return "404"


if __name__ == "__main__":
    app.run_server(debug=True, port=8080)

I would greatly appreciate it if someone could guide me on where exactly to place this scheduler code within my Dash app structure and how to run it effectively. I want to ensure that my Dash app runs smoothly and the scheduler app performs its tasks in the background without impacting the user experience.

Thank you in advance for your help!