# Create app for demo-job-monitoring ml.py
from sklearn.linear_model import LogisticRegression
import pandas as pd
import numpy as np

# Test prediction with a Female, 19 years old, earning 20000
fixed_value = [1, 19, 20000]

def preprocess(df: pd.DataFrame) -> pd.DataFrame:
    def _gender_to_int(gender):
        if gender == "Female":
            return 1
        return 0

    df["GenderNum"] = df["Gender"].apply(_gender_to_int)

    return df

def train(dataset):
    # X (features) are "GenderNum", "Age", "EstimatedSalary"
    X = dataset[["GenderNum", "Age", "EstimatedSalary"]]

    # Y is "Purchased"
    Y = dataset[["Purchased"]]

    # Let's split the dataset: the first 50 will be used for training,
    # the rest will be for testing
    split = 50
    X_train, Y_train = X[:split], Y[:split]
    X_test, Y_test = X[split:], Y[split:]

    # Using scikit-learn default
    regression = LogisticRegression(random_state=0, max_iter=10000).fit(
        X_train.values, Y_train.values.ravel()
    )

    # Accuracy of our model:
    print(f"intercept: {regression.intercept_} coefficients: {regression.coef_}")
    print(f"train accuracy: {regression.score(X_train, Y_train)}")
    print(f"test accuracy: {regression.score(X_test, Y_test)}")  # We aim for > 0.8...

    return regression

def predict(x, regression: LogisticRegression):
    variables = np.array(x).reshape(1, -1)
    result = regression.predict(variables)
    print(f"for: {variables}, the prediction is {result}")
    return result

if __name__ == "__main__":
    # Testing
    df = pd.read_csv("data/data.csv")
    df = preprocess(df)
    model = train(df)
    print(predict(fixed_value, model))


# Create app for demo-job-monitoring debug.py
import time

def long_running(anything):
    print("Waiting 20 seconds...")
    time.sleep(20)
    print("Done!")
    return anything

def raise_exception(anything):
    print("Waiting 5 seconds before raising an exception...")
    time.sleep(5)
    raise Exception("A very expected error occured!")


# Create app for demo-job-monitoring monitoring.py
import taipy as tp
from taipy.gui import get_state_id, invoke_callback, Markdown
from taipy.config.config import Config
from taipy.core.job.job import Job
from runtime import App

def get_all_jobs():
    """Returns all the known jobs (as a array of fields)."""

    def _job_to_fields(job: Job) -> list[str]:
        return [
            job.submit_id,
            job.id,
            job.creation_date.strftime("%b %d %Y %H:%M:%S"),
            str(job.status),
        ]

    return [_job_to_fields(job) for job in tp.get_jobs()]

def get_all_pipelines():
    """Returns all pipelines (as an array of ids)"""
    return [
        pipeline.id
        for pipeline in Config.pipelines.values()
        if pipeline.id != "default"  # we explicitely get rid of the "default" pipeline
    ]

def get_job_by_id(id):
    """Return a job from its id"""
    found = [job for job in tp.get_jobs() if job.id == id]
    if found:
        return found[0]
    return None

def get_job_by_index(index):
    """Return a job from its index"""
    all_jobs = tp.get_jobs()
    if len(all_jobs) > index:
        return all_jobs[index]
    return None

def get_status(job: Job):
    """Get the status of the given job as string."""
    if not job:
        return None
    return job.status.name.lower()

# -----------------------------------------------------------------------------
# Callbacks / UI function

def on_style(state, index, row):
    status_index = 3
    if 'RUNNING' in row[status_index]:
        return 'blue'
    if 'COMPLETED' in row[status_index]:
        return 'green'
    if 'BLOCKED' in row[status_index]:
        return 'orange'
    if 'FAILED' in row[status_index]:
        return 'red'

def refresh_job_list(state):
    """Refresh the job list"""
    state.all_jobs = get_all_jobs()

def job_updated(state_id, pipeline, job):
    """Callback called when a job has been updated."""

    # invoke_callback allows to run a function with a GUI _state_.
    invoke_callback(App().gui, state_id, refresh_job_list, args=[])

def open_run_pipeline_dialog(state):
    """Opens the 'Run pipeline...' dialog."""
    state.show_dialog_run_pipeline = True

def close_run_pipeline_dialog(state):
    """Closes the 'Run pipeline...' dialog."""
    state.show_dialog_run_pipeline = False

def run_pipeline(state):
    """Runs a pipeline action."""

    # We need to pass the state ID so that it can be restored in the job_updated listener:
    state_id = get_state_id(state)

    # Get selected pipeline config:
    selected = state.selected_pipeline
    pipeline_config = Config.pipelines[selected]
    if not pipeline_config:
        raise Exception(f"unknown pipeline config: {selected}")

    # Close the dialog
    close_run_pipeline_dialog(state)

    pipeline = tp.create_pipeline(pipeline_config)
    tp.subscribe_pipeline(pipeline=pipeline, callback=job_updated, params=[state_id])
    tp.submit(pipeline)

def on_table_click(state, table, action, payload):
    job_index = payload["index"]
    selected_job = get_job_by_index(job_index)
    state.selected_job = selected_job
    state.show_details_pane = True

def cancel_selected_job(state):
    job_id = state.selected_job.id
    tp.cancel_job(state.selected_job)
    state.show_details_pane = False
    refresh_job_list(state)
    state.selected_job = get_job_by_id(job_id)

def delete_selected_job(state):
    tp.delete_job(state.selected_job, force=True)
    state.show_details_pane = False
    refresh_job_list(state)

# -----------------------------------------------------------------------------
# UI Configuration

columns = {
    "0": {"title": "Submit ID"},
    "1": {"title": "Job ID"},
    "2": {"title": "Creation Date"},
    "3": {"title": "Status"},
}

# -----------------------------------------------------------------------------
# Page

page = Markdown("job_monitoring/pages/monitoring.md")


# Create app for demo-job-monitoring __init__.py


# Create app for demo-job-monitoring root.py
from taipy.gui import Markdown

content = """
# Job Monitoring Demo
"""

page = Markdown(content)


# Create app for demo-job-monitoring monitoring.md
<|{all_jobs}|table|columns={columns}|width='100%'|on_action={on_table_click}|style=on_style|>
<|Refresh List|button|on_action={refresh_job_list}|>
<|Run Pipeline...|button|on_action={open_run_pipeline_dialog}|>

<|{show_dialog_run_pipeline}|dialog|title=Run pipeline...|
<|{selected_pipeline}|selector|lov={get_all_pipelines()}|>
<|Run|button|on_action={run_pipeline}|>
<|Cancel|button|on_action={close_run_pipeline_dialog}|>
|>
<|{show_details_pane}|pane|

# Job Details <|Delete|button|on_action=delete_selected_job|> <|Cancel|button|on_action=cancel_selected_job|>

<|layout|columns=1 1|
<|part|class_name=card|
## Task
<|{selected_job.task.config_id}|>
|>

<|part|class_name=card|
## Status
<|{get_status(selected_job)}|>
|>
|>

<|part|class_name=card|
## ID
<|{selected_job.id}|>
|>

<|part|class_name=card|
## Submission ID
<|{selected_job.submit_id}|>
|>

<|part|class_name=card|
## Creation Date
<|{selected_job.creation_date.strftime("%b %d %y %H:%M:%S")}|>
|>

<|part|class_name=card|
## Stacktrace
<|{"\n".join(selected_job.stacktrace)}|class_name=code|>
|>

----

|>


# Create app for demo-fraud-detection charts.py
""" Prepare data for charts """

import pandas as pd

def gen_amt_data(transactions: pd.DataFrame) -> list:
    """
    Create a list of amt values for fraudulent and non-fraudulent transactions

    Args:
        - transactions: the transactions dataframe

    Returns:
        - a list of two dictionaries containing the data for the two histograms
    """
    amt_fraud = transactions[transactions["fraud"]]["amt"]
    amt_no_fraud = transactions[~transactions["fraud"]]["amt"]
    amt_data = [
        {"Amount ($)": list(amt_no_fraud)},
        {"Amount ($)": list(amt_fraud)},
    ]
    return amt_data

def gen_gender_data(transactions: pd.DataFrame) -> pd.DataFrame:
    """
    Create a dataframe containing the percentage of fraudulent transactions
    per gender

    Args:
        - transactions: the transactions dataframe

    Returns:
        - the resulting dataframe
    """
    male_fraud_percentage = len(
        transactions[transactions["fraud"]].loc[transactions["gender"] == "M"]
    ) / len(transactions[transactions["fraud"]])
    female_fraud_percentage = 1 - male_fraud_percentage
    male_not_fraud_percentage = len(
        transactions[~transactions["fraud"]].loc[transactions["gender"] == "M"]
    ) / len(transactions[~transactions["fraud"]])
    female_not_fraud_percentage = 1 - male_not_fraud_percentage

    gender_data = pd.DataFrame(
        {
            "Fraudulence": ["Not Fraud", "Fraud"],
            "Male": [male_not_fraud_percentage, male_fraud_percentage],
            "Female": [female_not_fraud_percentage, female_fraud_percentage],
        }
    )
    return gender_data

def gen_cat_data(transactions: pd.DataFrame) -> pd.DataFrame:
    """
    Generates a dataframe with the percentage difference
    between fraudulent and non-fraudulent transactions per category

    Args:
        - transactions: the transactions dataframe

    Returns:
        - the resulting dataframe
    """
    categories = transactions["category"].unique()
    fraud_categories = [
        len(
            transactions[transactions["fraud"]].loc[
                transactions["category"] == category
            ]
        )
        for category in categories
    ]
    fraud_categories_norm = [
        category / len(transactions[transactions["fraud"]])
        for category in fraud_categories
    ]
    not_fraud_categories = [
        len(
            transactions[~transactions["fraud"]].loc[
                transactions["category"] == category
            ]
        )
        for category in categories
    ]
    not_fraud_categories_norm = [
        category / len(transactions[~transactions["fraud"]])
        for category in not_fraud_categories
    ]
    diff_categories = [
        fraud_categories_norm[i] - not_fraud_categories_norm[i]
        for i in range(len(categories))
    ]
    cat_data = pd.DataFrame(
        {
            "Category": categories,
            "Difference": diff_categories,
        }
    )

    cat_data = cat_data.sort_values(by="Difference", ascending=False)
    return cat_data

def gen_age_data(transactions: pd.DataFrame) -> pd.DataFrame:
    """
    Generates a dataframe with the percentage of fraudulent transactions
    per age

    Args:
        - transactions: the transactions dataframe

    Returns:
        - the resulting dataframe
    """
    age = range(111)
    fraud_age = [
        len(transactions[transactions["fraud"]].loc[transactions["age"] == age])
        / len(transactions[transactions["fraud"]])
        for age in age
    ]
    not_fraud_age = [
        len(transactions[~transactions["fraud"]].loc[transactions["age"] == age])
        / len(transactions[~transactions["fraud"]])
        for age in age
    ]
    age_data = pd.DataFrame(
        {
            "Age": age,
            "Fraud": fraud_age,
            "Not Fraud": not_fraud_age,
        }
    )
    return age_data

def gen_hour_data(transactions: pd.DataFrame) -> pd.DataFrame:
    """
    Generates a dataframe with the percentage of fraudulent transactions
    per hour

    Args:
        - transactions: the transactions dataframe

    Returns:
        - the resulting dataframe
    """
    hours = range(1, 25)
    fraud_hours = [
        len(transactions[transactions["fraud"]].loc[transactions["hour"] == hour])
        / len(transactions[transactions["fraud"]])
        for hour in hours
    ]
    not_fraud_hours = [
        len(transactions[~transactions["fraud"]].loc[transactions["hour"] == hour])
        / len(transactions[~transactions["fraud"]])
        for hour in hours
    ]
    hour_data = pd.DataFrame(
        {
            "Hour": hours,
            "Fraud": fraud_hours,
            "Not Fraud": not_fraud_hours,
        }
    )
    return hour_data

def gen_day_data(transactions: pd.DataFrame) -> pd.DataFrame:
    """
    Generates a dataframe with the percentage of fraudulent transactions
    per weekday

    Args:
        - transactions: the transactions dataframe

    Returns:
        - the resulting dataframe
    """
    days = range(7)
    days_names = [
        "Monday",
        "Tuesday",
        "Wednesday",
        "Thursday",
        "Friday",
        "Saturday",
        "Sunday",
    ]
    fraud_days = [
        len(transactions[transactions["fraud"]].loc[transactions["day"] == day])
        / len(transactions[transactions["fraud"]])
        for day in days
    ]
    not_fraud_days = [
        len(transactions[~transactions["fraud"]].loc[transactions["day"] == day])
        / len(transactions[~transactions["fraud"]])
        for day in days
    ]
    day_data = pd.DataFrame(
        {
            "Day": days_names,
            "Fraud": fraud_days,
            "Not Fraud": not_fraud_days,
        }
    )
    return day_data

def gen_month_data(transactions: pd.DataFrame) -> pd.DataFrame:
    """
    Generates a dataframe with the percentage of fraudulent transactions
    per month

    Args:
        - transactions: the transactions dataframe

    Returns:
        - the resulting dataframe
    """
    months = range(1, 13)
    months_names = [
        "January",
        "February",
        "March",
        "April",
        "May",
        "June",
        "July",
        "August",
        "September",
        "October",
        "November",
        "December",
    ]
    fraud_months = [
        len(transactions[transactions["fraud"]].loc[transactions["month"] == month])
        / len(transactions[transactions["fraud"]])
        for month in months
    ]
    not_fraud_months = [
        len(transactions[~transactions["fraud"]].loc[transactions["month"] == month])
        / len(transactions[~transactions["fraud"]])
        for month in months
    ]
    month_data = pd.DataFrame(
        {
            "Month": months_names,
            "Fraud": fraud_months,
            "Not Fraud": not_fraud_months,
        }
    )
    return month_data


# Create app for demo-fraud-detection utils.py
""" Data Manipulation and Callbacks """
import datetime as dt
import numpy as np
import pandas as pd
from taipy.gui import State, navigate, notify
import xgboost as xgb
from shap import Explainer, Explanation
from sklearn.metrics import confusion_matrix

column_names = [
    "amt",
    "zip",
    "city_pop",
    "age",
    "hour",
    "day",
    "month",
    "category_food_dining",
    "category_gas_transport",
    "category_grocery_net",
    "category_grocery_pos",
    "category_health_fitness",
    "category_home",
    "category_kids_pets",
    "category_misc_net",
    "category_misc_pos",
    "category_personal_care",
    "category_shopping_net",
    "category_shopping_pos",
    "category_travel",
]

def explain_pred(state: State, _: str, payload: dict) -> None:
    """
    When a transaction is selected in the table
    Explain the prediction using SHAP, update the waterfall chart

    Args:
        - state: the state of the app
        - payload: the payload of the event containing the index of the transaction
    """
    idx = payload["index"]
    exp = state.explaination[idx]

    feature_values = [-value for value in list(exp.values)]
    data_values = list(exp.data)

    for i, value in enumerate(data_values):
        if isinstance(value, float):
            value = round(value, 2)
            data_values[i] = value

    names = [f"{name}: {value}" for name, value in zip(column_names, data_values)]

    exp_data = pd.DataFrame({"Feature": names, "Influence": feature_values})
    exp_data["abs_importance"] = exp_data["Influence"].abs()
    exp_data = exp_data.sort_values(by="abs_importance", ascending=False)
    exp_data = exp_data.drop(columns=["abs_importance"])
    exp_data = exp_data[:5]
    state.exp_data = exp_data

    if state.transactions.iloc[idx]["fraud"]:
        state.fraud_text = "Why is this transaction fraudulent?"
    else:
        state.fraud_text = "Why is this transaction not fraudulent?"

    first = state.transactions.iloc[idx]["first"]
    last = state.transactions.iloc[idx]["last"]

    state.specific_transactions = state.transactions[
        (state.transactions["first"] == first) & (state.transactions["last"] == last)
    ]

    state.selected_transaction = state.transactions.loc[[idx]]

    state.selected_client = f"{first} {last}"

    navigate(state, "Analysis")

def generate_transactions(
    state: State,
    df: pd.DataFrame,
    model: xgb.XGBRegressor,
    threshold: float,
    start_date="2020-06-21",
    end_date="2030-01-01",
) -> [pd.DataFrame, Explanation]:
    """
    Generates a DataFrame of transactions with the fraud prediction

    Args:
        - state: the state of the app
        - df: the DataFrame containing the transactions
        - model: the model used to predict the fraud
        - threshold: the threshold used to determine if a transaction is fraudulent
        - start_date: the start date of the transactions
        - end_date: the end date of the transactions

    Returns:
        - a DataFrame of transactions with the fraud prediction
    """
    start_date = str(start_date)
    end_date = str(end_date)
    start_date_dt = dt.datetime.strptime(start_date, "%Y-%m-%d")
    end_date_dt = dt.datetime.strptime(end_date, "%Y-%m-%d")
    # Make sure the dates are separated by at least one day
    if (end_date_dt - start_date_dt).days < 1:
        notify(state, "error", "The start date must be before the end date")
        raise Exception("The start date must be before the end date")
    # Make sure that start_date is between 2020-06-21 and 2020-06-30
    if not (dt.datetime(2020, 6, 21) <= start_date_dt <= dt.datetime(2020, 6, 30)):
        notify(
            state, "error", "The start date must be between 2020-06-21 and 2020-06-30"
        )
        raise Exception("The start date must be between 2020-06-21 and 2020-06-30")
    df["age"] = dt.date.today().year - pd.to_datetime(df["dob"]).dt.year
    df["hour"] = pd.to_datetime(df["trans_date_trans_time"]).dt.hour
    df["day"] = pd.to_datetime(df["trans_date_trans_time"]).dt.dayofweek
    df["month"] = pd.to_datetime(df["trans_date_trans_time"]).dt.month
    test = df[
        [
            "category",
            "amt",
            "zip",
            "city_pop",
            "age",
            "hour",
            "day",
            "month",
            "is_fraud",
        ]
    ]
    test = pd.get_dummies(test, drop_first=True)
    test = test[df["trans_date_trans_time"].between(str(start_date), str(end_date))]

    X_test = test.drop("is_fraud", axis="columns")
    X_test_values = X_test.values

    transactions = df[
        df["trans_date_trans_time"].between(str(start_date), str(end_date))
    ]
    raw_results = model.predict(X_test_values)
    results = [str(min(1, round(result, 2))) for result in raw_results]
    transactions.insert(0, "fraud_value", results)
    # Low if under 0.2, Medium if under 0.5, High if over 0.5
    results = ["Low" if float(result) < 0.2 else "Medium" for result in raw_results]
    for i, result in enumerate(results):
        if result == "Medium" and float(raw_results[i]) > 0.5:
            results[i] = "High"
    transactions.insert(0, "fraud_confidence", results)
    results = [float(result) > threshold for result in raw_results]
    transactions.insert(0, "fraud", results)

    explainer = Explainer(model)
    sv = explainer(X_test)
    explaination = Explanation(sv, sv.base_values, X_test, feature_names=X_test.columns)
    # Drop Unnamed: 0 column if it exists
    if "Unnamed: 0" in transactions.columns:
        transactions = transactions.drop(columns=["Unnamed: 0"])
    return transactions, explaination

def update_threshold(state: State) -> None:
    """
    Change the threshold used to determine if a transaction is fraudulent
    Generate the confusion matrix

    Args:
        - state: the state of the app
    """
    threshold = float(state.threshold)
    results = [
        float(result) > threshold for result in state.transactions["fraud_value"]
    ]
    state.transactions["fraud"] = results
    state.transactions = state.transactions
    results = [
        float(result) > threshold
        for result in state.original_transactions["fraud_value"]
    ]
    state.original_transactions["fraud"] = results
    state.original_transactions = state.original_transactions
    y_pred = results
    y_true = state.original_transactions["is_fraud"]
    cm = confusion_matrix(y_true, y_pred)
    cm = cm.astype("float") / cm.sum(axis=1)[:, np.newaxis]
    tp, tn, fp, fn = cm[1][1], cm[0][0], cm[0][1], cm[1][0]

    dataset = state.original_transactions[:10000]
    state.true_positives = dataset[
        (dataset["is_fraud"] == True) & (dataset["fraud"] == True)
    ]
    state.true_negatives = dataset[
        (dataset["is_fraud"] == False) & (dataset["fraud"] == False)
    ]
    state.false_positives = dataset[
        (dataset["is_fraud"] == False) & (dataset["fraud"] == True)
    ]
    state.false_negatives = dataset[
        (dataset["is_fraud"] == True) & (dataset["fraud"] == False)
    ]

    data = {
        "Values": [
            [fn, tp],
            [tn, fp],
        ],
        "Actual": ["Fraud", "Not Fraud"],
        "Predicted": ["Not Fraud", "Fraud"],
    }

    layout = {
        "annotations": [],
        "xaxis": {"ticks": "", "side": "top"},
        "yaxis": {"ticks": "", "ticksuffix": " "},
    }

    predicted = data["Predicted"]
    actuals = data["Actual"]
    for actual, _ in enumerate(actuals):
        for pred, _ in enumerate(predicted):
            value = data["Values"][actual][pred]
            annotation = {
                "x": predicted[pred],
                "y": actuals[actual],
                "text": f"{str(round(value, 3)*100)[:4]}%",
                "font": {"color": "white" if value < 0.5 else "black", "size": 30},
                "showarrow": False,
            }
            layout["annotations"].append(annotation)

    state.confusion_data = data
    state.confusion_layout = layout
    update_table(state)
    return (
        state.true_positives,
        state.true_negatives,
        state.false_positives,
        state.false_negatives,
    )

def update_table(state: State) -> None:
    """
    Updates the table of transactions displayed

    Args:
        - state: the state of the app
    """
    if state.selected_table == "True Positives":
        state.displayed_table = state.true_positives
    elif state.selected_table == "False Positives":
        state.displayed_table = state.false_positives
    elif state.selected_table == "True Negatives":
        state.displayed_table = state.true_negatives
    elif state.selected_table == "False Negatives":
        state.displayed_table = state.false_negatives


# Create app for demo-fraud-detection main.py
""" Fraud Detection App """
import pickle

import numpy as np
import pandas as pd
from taipy.gui import Gui, Icon, State, navigate, notify

from utils import (
    explain_pred,
    generate_transactions,
    update_threshold,
    update_table,
)
from charts import *

DATA_POINTS = 30000
threshold = "0.5"
threshold_lov = np.arange(0, 1, 0.01)
confusion_text = "Confusion Matrix"
fraud_text = "No row selected"
exp_data = pd.DataFrame({"Feature": [], "Influence": []})

df = pd.read_csv("data/fraud_data.csv")
df["merchant"] = df["merchant"].str[6:]
model = pickle.load(open("model.pkl", "rb"))
transactions, explaination = generate_transactions(None, df, model, float(threshold))
original_transactions = transactions
original_explaination = explaination
specific_transactions = transactions
selected_client = "No client selected"
start_date = "2020-06-21"
end_date = "2020-06-22"
selected_table = "True Positives"
true_positives = None
false_positives = None
true_negatives = None
false_negatives = None
displayed_table = None
selected_transaction = None

def fraud_style(_: State, index: int, values: list) -> str:
    """
    Style the transactions table: red if fraudulent

    Args:
        - state: the state of the app
        - index: the index of the row

    Returns:
        - the style of the row
    """
    if values["fraud_confidence"] == "High":
        return "red-row"
    elif values["fraud_confidence"] == "Medium":
        return "orange-row"
    return ""

amt_data = gen_amt_data(transactions)
gender_data = gen_gender_data(transactions)
cat_data = gen_cat_data(transactions)
age_data = gen_age_data(transactions)
hour_data = gen_hour_data(transactions)
day_data = gen_day_data(transactions)
month_data = gen_month_data(transactions)

df = df[:DATA_POINTS]
transactions = transactions[:DATA_POINTS]

waterfall_layout = {
    "margin": {"b": 150},
}

amt_options = [
    {
        "marker": {"color": "#4A4", "opacity": 0.8},
        "xbins": {"start": 0, "end": 2000, "size": 10},
        "histnorm": "probability",
    },
    {
        "marker": {"color": "#A33", "opacity": 0.8, "text": "Compare Data"},
        "xbins": {"start": 0, "end": 2000, "size": 10},
        "histnorm": "probability",
    },
]

amt_layout = {
    "barmode": "overlay",
    "showlegend": True,
}

confusion_data = pd.DataFrame({"Predicted": [], "Actual": [], "Values": []})
confusion_layout = None
confusion_options = {"colorscale": "YlOrRd", "displayModeBar": False}
confusion_config = {"scrollZoom": False, "displayModeBar": False}

transactions = df
transactions = transactions.drop("Unnamed: 0", axis="columns")

def on_init(state: State) -> None:
    """
    Generate the confusion matrix on start

    Args:
        - state: the state of the app
    """
    update_transactions(state)
    state.displayed_table = state.true_positives
    (
        state.true_positives,
        state.true_negatives,
        state.false_positives,
        state.false_negatives,
    ) = update_threshold(state)
    update_table(state)

def update_transactions(state: State) -> None:
    """
    Detects frauds in the selected time period

    Args:
        - state: the state of the app
    """
    notify(state, "info", "Predicting fraud...")
    state.transactions, state.explaination = generate_transactions(
        state, df, model, float(state.threshold), state.start_date, state.end_date
    )
    state.transactions.reset_index(inplace=True)
    number_of_fraud = len(state.transactions[state.transactions["fraud"] == True])
    notify(state, "success", f"Predicted {number_of_fraud} fraudulent transactions")

menu_lov = [
    ("Transactions", Icon("images/transactions.png", "Transactions")),
    ("Analysis", Icon("images/analysis.png", "Analysis")),
    ("Fraud Distribution", Icon("images/distribution.png", "Fraud Distribution")),
    ("Threshold Selection", Icon("images/threshold.png", "Threshold Selection")),
]

page = "Transactions"

def menu_fct(state, var_name, var_value):
    """Function that is called when there is a change in the menu control."""
    state.page = var_value["args"][0]
    navigate(state, state.page.replace(" ", "-"))

ROOT = """
<|menu|label=Menu|lov={menu_lov}|on_action=menu_fct|>
"""

TRANSACTIONS_PAGE = """
# List of **Transactions**{: .color-primary}

--------------------------------------------------------------------

## Select start and end date for a prediction
<|layout|columns=1 1 3|
Start Date: <|{start_date}|date|>

End Date (excluding): <|{end_date}|date|>
|>

<|Detect Frauds|button|on_action=update_transactions|>

## Select a transaction to explain the prediction

<|{transactions}|table|on_action=explain_pred|style=fraud_style|filter|rebuild|>
"""

ANALYSIS_PAGE = """
# Prediction **Analysis**{: .color-primary}

--------------------------------------------------------------------

<|layout|columns=2 3|
<|card|
## <|{fraud_text}|text|>
<|{exp_data}|chart|type=waterfall|x=Feature|y=Influence|layout={waterfall_layout}|>
|>

<|
## Selected Transaction:
<|{selected_transaction}|table|show_all=True|rebuild||style=fraud_style|>
## Transactions of client: **<|{selected_client}|text|raw|>**{: .color-primary}
<|{specific_transactions}|table|style=fraud_style|filter|on_action=explain_pred|>
|>
|>
"""

CHART_PAGE = """
# Fraud **Distribution**{: .color-primary}

--------------------------------------------------------------------

## Charts of fraud distribution by feature

<|{amt_data}|chart|type=histogram|title=Transaction Amount Distribution|color[2]=red|color[1]=green|name[2]=Fraud|name[1]=Not Fraud|options={amt_options}|layout={amt_layout}|>

<|{gender_data}|chart|type=bar|x=Fraudulence|y[1]=Male|y[2]=Female|title=Distribution of Fraud by Gender|>

<|{cat_data}|chart|type=bar|x=Category|y=Difference|orientation=v|title=Difference in Fraudulence by Category (Positive = Fraudulent)|>

<|{hour_data}|chart|type=bar|x=Hour|y[1]=Not Fraud|y[2]=Fraud|title=Distribution of Fraud by Hour|>

<|{day_data}|chart|type=bar|x=Day|y[1]=Not Fraud|y[2]=Fraud|title=Distribution of Fraud by Day|>
"""

THRESHOLD_PAGE = """
# Threshold **Selection**{: .color-primary}

--------------------------------------------------------------------

## Select a threshold of confidence to filter the transactions
<|{threshold}|slider|on_change=update_threshold|lov=0.05;0.1;0.15;0.2;0.25;0.3;0.35;0.4;0.45;0.5;0.55;0.6;0.65;0.7;0.75;0.8;0.85;0.9;0.95|>
<|layout|columns=1 2|
<|{confusion_data}|chart|type=heatmap|z=Values|x=Predicted|y=Actual|layout={confusion_layout}|options={confusion_options}|plot_config={confusion_config}|height=70vh|>

<|card
<|{selected_table}|selector|lov=True Positives;False Positives;True Negatives;False Negatives|on_change=update_table|dropdown=True|>
<|{displayed_table}|table|style=fraud_style|filter|rebuild|>
|>
|>
"""

pages = {
    "/": ROOT,
    "Transactions": TRANSACTIONS_PAGE,
    "Analysis": ANALYSIS_PAGE,
    "Fraud-Distribution": CHART_PAGE,
    "Threshold-Selection": THRESHOLD_PAGE,
}

Gui(pages=pages).run(title="Fraud Detection Demo", dark_mode=False, debug=True)


# Create app for dask_taipy_bigdata_DEMO algo.py
import time
import pandas as pd
import dask.dataframe as dd

def task1(path_to_original_data: str):
    print("__________________________________________________________")
    print("1. TASK 1: DATA PREPROCESSING AND CUSTOMER SCORING ...")
    start_time = time.perf_counter()  # Start the timer

    # Step 1: Read data using Dask
    df = dd.read_csv(path_to_original_data)

    # Step 2: Simplify the customer scoring formula
    df['CUSTOMER_SCORE'] = (
        0.5 * df['TotalPurchaseAmount'] / 1000 +
        0.3 * df['NumberOfPurchases'] / 10 +
        0.2 * df['AverageReviewScore']
    )

    # Save all customers to a new CSV file
    scored_df = df[["CUSTOMER_SCORE", "TotalPurchaseAmount", "NumberOfPurchases", "TotalPurchaseTime"]]

    pd_df = scored_df.compute()

    end_time = time.perf_counter()  # Stop the timer
    execution_time = (end_time - start_time) * 1000  # Calculate the time in milliseconds
    print(f"Time of Execution: {execution_time:.4f} ms")

    return pd_df

def task2(scored_df, payment_threshold, score_threshold):
    print("__________________________________________________________")
    print("2. TASK 2: FEATURE ENGINEERING AND SEGMENTATION ...")

    payment_threshold, score_threshold = float(payment_threshold), float(score_threshold)
    start_time = time.perf_counter()  # Start the timer

    df = scored_df

    # Feature: Indicator if customer's total purchase is above the payment threshold
    df['HighSpender'] = (df['TotalPurchaseAmount'] > payment_threshold).astype(int)

    # Feature: Average time between purchases
    df['AverageTimeBetweenPurchases'] = df['TotalPurchaseTime'] / df['NumberOfPurchases']

    # Additional computationally intensive features
    df['Interaction1'] = df['TotalPurchaseAmount'] * df['NumberOfPurchases']
    df['Interaction2'] = df['TotalPurchaseTime'] * df['CUSTOMER_SCORE']
    df['PolynomialFeature'] = df['TotalPurchaseAmount'] ** 2

    # Segment customers based on the score_threshold
    df['ValueSegment'] = ['High Value' if score > score_threshold else 'Low Value' for score in df['CUSTOMER_SCORE']]

    end_time = time.perf_counter()  # Stop the timer
    execution_time = (end_time - start_time) * 1000  # Calculate the time in milliseconds
    print(f"Time of Execution: {execution_time:.4f} ms")

    return df

def task3(df: pd.DataFrame, metric):
    print("__________________________________________________________")
    print("3. TASK 3: SEGMENT ANALYSIS ...")
    start_time = time.perf_counter()  # Start the timer

    # Detailed analysis for each segment: mean/median of various metrics
    segment_analysis = df.groupby('ValueSegment').agg({
        'CUSTOMER_SCORE': metric,
        'TotalPurchaseAmount': metric,
        'NumberOfPurchases': metric,
        'TotalPurchaseTime': metric,
        'HighSpender': 'sum',  # Total number of high spenders in each segment
        'AverageTimeBetweenPurchases': metric
    }).reset_index()

    end_time = time.perf_counter()  # Stop the timer
    execution_time = (end_time - start_time) * 1000  # Calculate the time in milliseconds
    print(f"Time of Execution: {execution_time:.4f} ms")

    return segment_analysis

def task4(df: pd.DataFrame, segment_analysis: pd.DataFrame, summary_statistic_type: str):
    print("__________________________________________________________")
    print("4. TASK 4: ADDITIONAL ANALYSIS BASED ON SEGMENT ANALYSIS ...")
    start_time = time.perf_counter()  # Start the timer

    # Filter out the High Value customers
    high_value_customers = df[df['ValueSegment'] == 'High Value']

    # Use summary_statistic_type to calculate different types of summary statistics
    if summary_statistic_type == 'mean':
        average_purchase_high_value = high_value_customers['TotalPurchaseAmount'].mean()
    elif summary_statistic_type == 'median':
        average_purchase_high_value = high_value_customers['TotalPurchaseAmount'].median()
    elif summary_statistic_type == 'max':
        average_purchase_high_value = high_value_customers['TotalPurchaseAmount'].max()
    elif summary_statistic_type == 'min':
        average_purchase_high_value = high_value_customers['TotalPurchaseAmount'].min()

    median_score_high_value = high_value_customers['CUSTOMER_SCORE'].median()

    # Fetch the summary statistic for 'TotalPurchaseAmount' for High Value customers from segment_analysis
    segment_statistic_high_value = segment_analysis.loc[segment_analysis['ValueSegment'] == 'High Value', 'TotalPurchaseAmount'].values[0]

    # Create a DataFrame to hold the results
    result_df = pd.DataFrame({
        'SummaryStatisticType': [summary_statistic_type],
        'AveragePurchaseHighValue': [average_purchase_high_value],
        'MedianScoreHighValue': [median_score_high_value],
        'SegmentAnalysisHighValue': [segment_statistic_high_value]
    })

    end_time = time.perf_counter()  # Stop the timer
    execution_time = (end_time - start_time) * 1000  # Calculate the time in milliseconds
    print(f"Time of Execution: {execution_time:.4f} ms")

    return result_df

if __name__ == "__main__":

    t1 = task1("data/SMALL_amazon_customers_data.csv")

    t2 = task2(t1, 1500, 1.5)

    t3 = task3(t2, "mean")

    t4 = task4(t2, t3, "mean")

    print(t4)


# Create app for demo-image-classification-part-2 readme.md
# Image Classification Part 2 Using Taipy Core

## Usage
- [Usage](#usage)
- [Image Classification Part 2](#what-is-image-classification-part-2)
- [Directory Structure](#directory-structure)
- [License](#license)
- [Installation](#installation)
- [Contributing](#contributing)
- [Code of conduct](#code-of-conduct)

## What is Image Classification Part 2

Taipy is a Python library for creating Business Applications. More information on our
[website](https://www.taipy.io).

[Image Classification Part 2](https://github.com/Avaiga/image-classification-part-2) is about how to use Taipy Core and Taipy Studio to efficiently create and manage Data and ML pipelines.

### Demo Type
- **Level**: Intermediate
- **Topic**: Taipy-CORE
- **Components/Controls**:
  - Taipy CORE: configs, Taipy Studio

## How to run

This demo works with a Python version superior to 3.8. Install the dependencies of the *Pipfile* and run the *main.py*.

## Introduction
The Demo is the second part of the Image Classification App using Taipy and Tensorflow, and it is recommended to watch the first part or go through the repo to understand the main functions and tasks.

The video on Youtubecovers using Taipy Core and Taipy Studio to build pipelines and manage different scenarios. The demo covers copying necessary functions into a script file, configuring datanodes, specifying functions for tasks, configuring pipelines, and executing the scenario with Taipy Studio and/or Taipy Core.

## Directory Structure

- `src/`: Contains the demo source code.
- `docs/`: contains the images for the documentation
- `CODE_OF_CONDUCT.md`: Code of conduct for members and contributors of _image-classification-part-2_.
- `CONTRIBUTING.md`: Instructions to contribute to _image-classification-part-2_.
- `INSTALLATION.md`: Instructions to install _image-classification-part-2_.
- `LICENSE`: The Apache 2.0 License.
- `Pipfile`: File used by the Pipenv virtual environment to manage project dependencies.
- `README.md`: Current file.

## License
Copyright 2022 Avaiga Private Limited

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
[http://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt)

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.

## Installation

Want to install _image-classification-part-2_? Check out our [`INSTALLATION.md`](INSTALLATION.md) file.

## Contributing

Want to help build _image-classification-part-2_? Check out our [`CONTRIBUTING.md`](CONTRIBUTING.md) file.

## Code of conduct

Want to be part of the _image-classification-part-2_ community? Check out our [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) file.


# Create app for demo-image-classification-part-2 config_from_tp_studio.py
from main_functions import *
from taipy import Config
import taipy as tp

Config.load('built_with_tp_studio.toml')
scenario_cfg = Config.scenarios['testing_scenario']

tp.Core().run()
main_scenario = tp.create_scenario(scenario_cfg)
tp.submit(main_scenario)


# Create app for demo-image-classification-part-2 main_functions.py
import tensorflow as tf
from tensorflow.keras import layers, models
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.utils import to_categorical
import pandas as pd
import numpy as np

class_names =  ['AIRPLANE', 'AUTOMOBILE', 'BIRD', 'CAT', 'DEER', 'DOG', 'FROG', 'HORSE', 'SHIP', 'TRUCK']

(x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()

x_train = x_train / 255.0
y_train = to_categorical(y_train, len(class_names))
x_test = x_test / 255.0
y_test = to_categorical(y_test, len(class_names))

def tf_read(path: str): return tf.keras.models.load_model(path)
def tf_write(model, path: str):model.save(path)

#Task 1.1: Building the base model
def initialize_model(loss_f):
  # Creating model base
    model = models.Sequential()
    model.add(layers.Conv2D(32, (3, 3), activation='relu', padding='same', input_shape=(32, 32, 3)))
    model.add(layers.Conv2D(32, (3, 3), activation='relu', padding='same'))
    model.add(layers.MaxPool2D((2,2)))

    model.add(layers.Conv2D(64, (3, 3), activation='relu', padding='same',))
    model.add(layers.Conv2D(64, (3, 3), activation='relu', padding='same',))
    model.add(layers.MaxPool2D((2,2)))

    model.add(layers.Conv2D(128, (3, 3), activation='relu', padding='same',))
    model.add(layers.Conv2D(128, (3, 3), activation='relu', padding='same',))
    model.add(layers.MaxPool2D((2,2)))

    model.add(layers.Flatten())
    model.add(layers.Dense(128, activation='relu'))
    model.add(layers.Dense(10, activation='softmax'))

    model.compile(optimizer='adam',
                    loss=loss_f,
                    metrics=['accuracy'])

    return model

#Task 1.2: Initial training witha fixed number of epochs
datagen = ImageDataGenerator(
    horizontal_flip=True,
    width_shift_range=3/32,
    height_shift_range=3/32
)

def initial_model_training(n_epochs, model):
    print("INITIAL MODEL TRAINING STARTED: ")

    h = model.fit(
        datagen.flow(x_train, y_train, batch_size=64),
        epochs=n_epochs,
        validation_data=(x_test, y_test))

    training_result = pd.DataFrame.from_dict(h.history)
    training_result["N_Epochs"] = range(1,len(training_result)+1)

    return training_result, model

#Task 2.1: Merge train with a chosen number of epochs (training + validation set as training)
def merged_train(number_of_epochs,model):
    print("MERGED TRAIN STARTED: ")
    # merge the training and validation sets
    x_all = np.concatenate((x_train, x_test))
    y_all = np.concatenate((y_train, y_test))

    h = model.fit(
        datagen.flow(x_all, y_all, batch_size=64),
        epochs=number_of_epochs)

    training_result = pd.DataFrame.from_dict(h.history)
    training_result["N_Epochs"] = range(1,len(training_result)+1)

    return training_result, model

#Task 2.2: Predict image class
def predict_image(image_path, trained_model):
    print("PREDICTION TASK STARTED: ")
    img_array = tf.keras.utils.load_img(image_path, target_size=(32, 32))
    image = tf.keras.utils.img_to_array(img_array)
    image = np.expand_dims(image, axis=0) / 255.
    prediction_result = class_names[np.argmax(trained_model.predict(image))]
    print("Prediction result: {}".format(prediction_result))
    return prediction_result


# Create app for demo-image-classification-part-2 main.py
from main_functions import *
from taipy import Config
import taipy as tp

#######################################################################################################
##############################################PIPELINE 1###############################################
#######################################################################################################

###TASK 1.1: Building the base model
#input dn
loss_fn_cfg = Config.configure_data_node("loss_fn", default_data='categorical_crossentropy')
#output dn
base_model_cfg = Config.configure_generic_data_node("base_model",
                                                    read_fct=tf_read, read_fct_params=('models/base_model',),
                                                    write_fct=tf_write, write_fct_params=('models/base_model',))
#task
BUILD_CNN_BASE_cfg = Config.configure_task("BUILD_CNN_BASE",
                                    initialize_model,
                                    loss_fn_cfg,
                                    base_model_cfg)

###TASK 1.2: Initial training with a fixed number of epochs
#input dn
initial_n_epochs_cfg = Config.configure_data_node("initial_n_epochs", default_data=30)
#output dn
initial_train_perf_cfg = Config.configure_data_node("initial_train_perf")
trained_initial_model_cfg = Config.configure_generic_data_node("trained_initial_model",
                                                    read_fct=tf_read, read_fct_params=('models/trained_initial_model',),
                                                    write_fct=tf_write, write_fct_params=('models/trained_initial_model',))
#task
INITIAL_TRAIN_cfg = Config.configure_task("INITIAL_TRAIN",
                                    initial_model_training,
                                    [initial_n_epochs_cfg, base_model_cfg],
                                    [initial_train_perf_cfg, trained_initial_model_cfg])
#pipeline
pipeline_1_cfg = Config.configure_pipeline("pipeline_1",
                                               [BUILD_CNN_BASE_cfg,
                                                INITIAL_TRAIN_cfg])

#######################################################################################################
##############################################PIPELINE 2###############################################
#######################################################################################################

###TASK 2.1: Merge train with a chosen number of epochs (training + validation set as training)
#input dn
optimal_n_epochs_cfg = Config.configure_data_node("optimal_n_epochs", default_data=13)
#output dn
merged_train_perf_cfg = Config.configure_data_node("merged_train_perf")
merged_trained_model_cfg = Config.configure_generic_data_node("merged_trained_model",
                                                    read_fct=tf_read, read_fct_params=('models/merged_trained_model',),
                                                    write_fct=tf_write, write_fct_params=('models/merged_trained_model',))
#task
MERGED_TRAIN_cfg = Config.configure_task("MERGED_TRAIN",
                                    merged_train,
                                    [optimal_n_epochs_cfg, base_model_cfg],
                                    [merged_train_perf_cfg, merged_trained_model_cfg])

###TASK 2.2: Make a prediction from an image path
#input dn: the trained model datanode, already set up
image_path_dn_cfg = Config.configure_data_node("image_path_dn", default_data="test_images/dog.jpg")
#output dn
prediction_cfg = Config.configure_data_node("image_prediction")
#task
IMAGE_PREDICT_cfg = Config.configure_task("IMAGE_PREDICT", predict_image,
                                          [image_path_dn_cfg, merged_trained_model_cfg],
                                          [prediction_cfg])
#pipeline
pipeline_2_cfg = Config.configure_pipeline("pipeline_2",
                                               [MERGED_TRAIN_cfg,
                                                IMAGE_PREDICT_cfg])

#######################################################################################################
##############################################Scenario#################################################
#######################################################################################################
scenario_cfg = Config.configure_scenario("testing_scenario",
                                         [pipeline_1_cfg, pipeline_2_cfg])

tp.Core().run()
main_scenario = tp.create_scenario(scenario_cfg)
tp.submit(main_scenario)
Config.export("tpcore.toml")


# Create app for demo-edit-log LICENSE.md

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright 2022 Avaiga Private Limited

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


# Create app for demo-edit-log main.py
from taipy.gui import Gui
import taipy as tp
from taipy.gui import notify
from config.config import *

# Variables for bindings
all_scenarios = []  # List of scenarios
all_scenarios_configs = []  # List of scenario configs
all_data_nodes = []  # List of node IDs

current_scenario = None
current_data_node = None
current_scenario_config = None

scenario_name = None
edits = [["","",""]]

value = None
commit_message = ""
create_scenario_dialog_visible = False
set_value_dialog_visible = False

# ====================================================================

def on_init(state):
    state.all_scenarios = [(sc.id, sc.name) for sc in tp.get_scenarios()]
    state.all_scenarios_configs = [sc.id for sc in Config.scenarios.values()]

def on_change_current_scenario(state):
    scenario = tp.get(state.current_scenario[0])
    # Propagate to list of nodes:
    state.all_data_nodes = [(dn.id, dn.config_id) for dn in scenario.data_nodes.values()]

def on_change(state, var_name: str, var_value):
    if var_name == "all_data_nodes":
        # Propagate to current data node (pick any...):
        if var_value and len(var_value) > 0:
            data_node = next(iter(var_value))
            state.current_data_node = data_node
    if var_name == "current_data_node":
        # Propagate to list of edits:
        refresh_edit_log(state)

def refresh_edit_log(state):
    # Forces a refresh of the edit log:
    if state.current_data_node:
        data_node_id = state.current_data_node[0]
        data_node = tp.get(data_node_id)
        state.edits = get_edit_log(data_node) if data_node else []

def create_scenario_clicked(state):
    state.scenario_name = None
    state.create_scenario_dialog_visible = True

def get_edit_log(data_node):
    def _get_edit_fields(edit):
        return [str(edit.get("timestamp")), edit.get("job_id"), edit.get("message")]

    return [_get_edit_fields(edit) for edit in data_node.edits] if data_node else []

def on_submit_button_clicked(state):
    scenario_id = state.current_scenario[0]
    scenario = tp.get(scenario_id)
    tp.submit(scenario)
    # Force refresh of current data node:
    refresh_edit_log(state)
    notify(state, message=f"Scenario {scenario.name} submitted!")

def on_set_value_clicked(state):
    state.set_value_dialog_visible = True

def create_scenario_dialog_action(state, id, action, payload):
    state.create_scenario_dialog_visible = False
    btn_idx = payload["args"][0]
    if btn_idx == 0:  # OK button
        scenario_cfg = Config.scenarios[state.current_scenario_config]
        name = state.scenario_name
        scenario = tp.create_scenario(config=scenario_cfg, name=name)
        all_scenarios = state.all_scenarios
        all_scenarios.append((scenario.id, scenario.name))
        state.all_scenarios = all_scenarios
        notify(state, message=f"Scenario {scenario.name} created!")

def set_value_dialog_action(state, id, action, payload):
    btn_idx = payload["args"][0]
    if btn_idx == 0:  # OK button
        data_node_id = state.current_data_node[0]
        node = tp.get(data_node_id)
        node.write(state.value, message=state.commit_message)
        state.current_data_node = state.current_data_node

    state.set_value_dialog_visible = False

history_table_columns = {
    "0": {"title": "Date"},
    "1": {"title": "Job Id"},
    "2": {"title": "Comments"},
}

scenario_manager_page = """
<|part|class_name=card|
## Data Node Selection
<|{current_scenario}|selector|lov={all_scenarios}|dropdown|label=<select a scenario>|on_change=on_change_current_scenario|>
<|{current_data_node}|selector|lov={all_data_nodes}|dropdown|label=<select a data node>|>

<|Create New Scenario...|button|on_action=create_scenario_clicked|>
<|Run Scenario|button|active={current_scenario is not None}|on_action=on_submit_button_clicked|>
|>

<|part|class_name=card|
## Data Node Edit Log
<|{edits}|table|columns={history_table_columns}|width=50vw|>
<|Refresh|button|on_action=refresh_edit_log|>
<|Set value...|button|active={len(edits) > 0}|on_action=on_set_value_clicked|>
|>

<|{create_scenario_dialog_visible}|dialog|title=Create Scenario|labels=OK;Cancel|on_action=create_scenario_dialog_action|

Select a scenario config:
<|{current_scenario_config}|selector|dropdown|lov={all_scenarios_configs}|>

Enter a name for your scenario:

<|{scenario_name}|input|change_delay=10|>
|>

<|{set_value_dialog_visible}|dialog|title=Set value|labels=OK;Cancel|change_delay=10|on_action=set_value_dialog_action|
<|{value}|input|label=Enter a value|>

<|Optional commit message|expandable|expanded=False|
<|{commit_message}|input|>
|>
|>
"""

if __name__ == "__main__":
    gui = Gui(page=scenario_manager_page)
    core = tp.Core()
    tp.run(core, gui, port=8080, dark_mode=False)


# Create app for demo-edit-log config.py
from algos.algos import task_function
from taipy import Config

Config.configure_job_executions(mode="standalone", max_nb_of_workers=1)

node_start_cfg = Config.configure_data_node(
    id="node_start", default_data=[1, 2], description="This is the initial data node."
)
node_end_cfg = Config.configure_data_node(id="node_end", description="This is the result data node.")
task_cfg = Config.configure_task(id="task", input=[node_start_cfg], output=node_end_cfg, function=task_function)
pipeline_cfg = Config.configure_pipeline(id="pipeline", task_configs=[task_cfg])
Config.configure_scenario("My_super_scenario", [pipeline_cfg])


# Create app for demo-edit-log algos.py
def task_function(data):
    """A dummy task function"""
    print(f"Executing function: {data}")
    return data


# Create app for demo-face-recognition LICENSE.md

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright 2022 Avaiga Private Limited

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


# Create app for demo-face-recognition find_taipy_gui_dir.py
# This Python script tries to locate the taipy.gui package, and
# prints its absolute path if it finds it.
import importlib.util
import os

taipy_gui = importlib.util.find_spec("taipy.gui")
if taipy_gui is None:
    print("Cannot find 'taipy.gui'\nPlease run 'pip install taipy-gui'.")
else:
    print(f"Taipy GUI location: {os.path.dirname(taipy_gui.origin)}")


# Create app for demo-face-recognition GETTING_STARTED.md
# Getting Started

## Installation
First you need to install the dependencies and build the front-end. Please refer to [INSTALLATION.md](INSTALLATION.md).

## How to use the demo

Once you started the application, your default Web browser should open automatically. If not, open this URL: [http://127.0.0.1:9090](http://127.0.0.1:9090).
The browser should ask you for the authorization to use the camera. Press "Allow".

Once allowed, your camera should activate and you will see a live view of the video. Notice that your face your already be detected and the label "None" is displayed. This is because the application does not know you yet.

To train the app to recognize your face, press the "Capture" button. This will show a dialog with a captured image. Enter a name for that face and press "validate".
The more training examples, the better. So add few more captured images of your faces.

Notice that the case of the given name is important. So always use the same name for captured image.
Example: "Greg" and "greg" will be considered as two different names.

After say 6 different images, you can ask the system to learn from them by pressing the "Re-train" button.
Depending on the number of images to process, this can take from a second to a dozen of seconds.

The application will then be able to recognize the new face, and the name should be displayed on screen!


# Create app for demo-face-recognition main.py
from taipy.gui import Gui
from webcam import Webcam
import cv2

import PIL.Image
import io

import logging
import uuid
from pathlib import Path
from demo.faces import detect_faces, recognize_face, train_face_recognizer

logging.basicConfig(level=logging.DEBUG)

training_data_folder = Path("images")

show_capture_dialog = False
capture_image = False
show_add_captured_images_dialog = False

labeled_faces = []  # Contains rect with label (for UI component)

captured_image = None
captured_label = ""

def on_action_captured_image(state, id, action, payload):
    print("Captured image")
    choice = payload["args"][0]
    if choice == 0:
        # Add image to training data:
        img = state.captured_image
        file_name = str(uuid.uuid4()) + ".jpg"
        label = state.captured_label
        image_path = Path(training_data_folder, file_name)
        with image_path.open("wb") as f:
            f.write(img)
        label_file_path = Path(training_data_folder, "data.csv")
        with label_file_path.open("a") as f:
            f.write(f"{file_name},{label}\n")

    state.captured_image = None
    state.captured_label = ""
    state.show_capture_dialog = False

def process_image(state, frame):
    print("Processing image...")
    found = detect_faces(frame)

    labeled_images = []
    for rect, img in found:
        (label, _) = recognize_face(img)
        labeled_images.append((img, rect, label))

    # Return this to the UI component so that it can display a rect around recognized faces:
    state.labeled_faces = [str([*rect, label]) for (_, rect, label) in labeled_images]

    # Capture image (actually we consider only the first detected face)
    if state.capture_image and len(labeled_images) > 0:
        img = labeled_images[0][0]
        label = labeled_images[0][2]
        state.captured_image = cv2.imencode(".jpg", img)[1].tobytes()
        state.captured_label = label
        state.show_capture_dialog = True
        state.capture_image = False

def handle_image(state, action, args, value):
    print("Handling image...")
    payload = value["args"][0]
    bytes = payload["data"]
    logging.debug(f"Received data: {len(bytes)}")

    temp_path = "temp.png"

    # Write Data into temp file (OpenCV is unable to load from memory)
    image = PIL.Image.open(io.BytesIO(bytes))
    image.save(temp_path)
    # Load image file
    try:
        img = cv2.imread(temp_path, cv2.IMREAD_UNCHANGED)
    except cv2.error as e:
        logging.error(f"Failed to read image file: {e}")
        return
    process_image(state, img)
    # Finish. Tempfile is removed.

def button_retrain_clicked(state):
    print("Retraining...")
    train_face_recognizer(training_data_folder)

webcam_md = """<|toggle|theme|>

<container|container|part|

# Face **recognition**{: .color-primary}

This demo shows how to use [Taipy](https://taipy.io/) with a [custom GUI component](https://docs.taipy.io/en/latest/manuals/gui/extension/) to capture video from your webcam and do realtime face detection. What this application demonstrates:

- How to build a complex custom UI component for Taipy.

- How to detect and recognize faces in the image in real time using [OpenCV](https://opencv.org/).

<br/>

<card|card p-half|part|
## **Webcam**{: .color-primary} component

<|text-center|part|
<|webcam.Webcam|faces={labeled_faces}|classname=face_detector|id=my_face_detector|on_data_receive=handle_image|sampling_rate=100|>

<|Capture|button|on_action={lambda s: s.assign("capture_image", True)}|>
<|RE-train|button|on_action=button_retrain_clicked|>
>
|card>
|container>

<|{show_capture_dialog}|dialog|labels=Validate;Cancel|on_action=on_action_captured_image|title=Add new training image|
<|{captured_image}|image|width=300px|height=300px|>

<|{captured_label}|input|>
|>
"""

if __name__ == "__main__":
    # Create dir where the pictures will be stored
    if not training_data_folder.exists():
        training_data_folder.mkdir()

    train_face_recognizer(training_data_folder)

    gui = Gui(webcam_md)
    gui.add_library(Webcam())
    gui.run(port=9090)


# Create app for demo-face-recognition faces.py
import cv2
from pathlib import Path
import os
import numpy as np
import logging
from .image import crop_image
import pandas as pd

logging.basicConfig(level=logging.DEBUG)

# Create our face detector. Both HAAR and LBP classifiers are somehow equivelent and both give good results.
# Up to you to choose one or the other.
face_detector = cv2.CascadeClassifier("classifiers/haarcascade_frontalface_default.xml")
# face_cascade = cv2.CascadeClassifier("classifiers/lbpcascade_frontalface_improved.xml")

# Create our face recognizer
face_recognizer = cv2.face.LBPHFaceRecognizer_create()

# The subjects that can be recognized
subjects = {}

FACE_DETECTOR_SCALE_FACTOR = 1.1
FACE_DETECTOR_MIN_NEIGHBORS = 5

def detect_faces(image):
    gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    detected_faces = face_detector.detectMultiScale(
        gray_image,
        scaleFactor=FACE_DETECTOR_SCALE_FACTOR,
        minNeighbors=FACE_DETECTOR_MIN_NEIGHBORS,
    )
    if len(detected_faces) == 0:
        return []

    return [(rect, crop_image(image, rect)) for rect in detected_faces]

def recognize_face(image):
    gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    if len(subjects) == 0:
        # No subject, the model hasn't been trained, let's do nothing.
        return (None, None)
    try:
        face = face_recognizer.predict(gray_image)
    except Exception:
        logging.warning("Could not run face recognizer", exc_info=True)
    # Return the name of the recognize subject and the confident level
    return (subjects[face[0]], face[1])

def train_face_recognizer(training_data_directory="images"):
    data_file_path = Path(training_data_directory, "data.csv")
    if not data_file_path.exists():
        # Create file
        with data_file_path.open("w") as f:
            f.write("image,label\n")

    # Load file as CSV file
    data = pd.read_csv(data_file_path, delimiter=",", header=0).to_numpy()

    # Subjects that can be recognized from these data:
    identified_subjects = np.unique(data[:, 1])
    global subjects

    if len(identified_subjects) == 0:
        # No subject... We stop here
        subjects = {}
        return
    else:
        # Update subjects (persons who can be recognized)
        subjects = {e[0]: e[1] for e in enumerate(identified_subjects)}

    # Prepare training data
    faces, labels = [], []
    for row in data:
        file_name = row[0]
        label = np.where(identified_subjects == row[1])[0][0]
        file_path = Path(training_data_directory, file_name)
        if os.path.exists(file_path):
            img = cv2.imread(str(file_path), cv2.IMREAD_GRAYSCALE)
            faces.append(img)
            labels.append(label)

    # Run training!
    logging.debug(f"Run training for {subjects}...")
    face_recognizer.train(faces, np.array(labels))


# Create app for demo-face-recognition __init__.py


# Create app for demo-face-recognition image.py
def crop_image(img, rect):
    """An utility function to crop an image to the given rect"""
    x, y, w, h = rect
    return img[y : y + h, x : x + w]


# Create app for demo-face-recognition __init__.py
from .webcam import Webcam


# Create app for demo-face-recognition webcam.py
from taipy.gui.extension import ElementLibrary, Element, ElementProperty, PropertyType

class Webcam(ElementLibrary):
    def get_name(self) -> str:
        return "webcam"

    def get_elements(self) -> dict:
        return {
            "Webcam": Element(
                "faces",
                {
                    "faces": ElementProperty(PropertyType.dynamic_list),
                    "id": ElementProperty(PropertyType.string),
                    "classname": ElementProperty(PropertyType.dynamic_string),
                    "on_data_receive": ElementProperty(PropertyType.string),
                    "sampling_rate": ElementProperty(PropertyType.number),
                },
                react_component="Webcam",
            )
        }

    def get_scripts(self) -> list[str]:
        return ["webui/dist/webcam.js"]


# Create app for demo-taipy-gui-starter-2 main.py
from taipy.gui import Gui, notify
import pandas as pd
import webbrowser
import datetime
import os

DOWNLOAD_PATH = "data/download.csv"
upload_file = None

section_1 = """

<|navbar|lov={[("page1", "This Page"), ("https://docs.taipy.io/en/latest/manuals/about/", "Taipy Docs"), ("https://docs.taipy.io/en/latest/getting_started/", "Getting Started")]}|>

Data Dashboard with Taipy
=========================
<|layout|columns=1 3|
<|
### Let's create a simple Data Dashboard!

    <|{upload_file}|file_selector|label=Upload Dataset|>

|>
<|

    <|{logo}|image|height=250px|width=250px|on_action=image_action|>

|>
|>
"""

section_2 = """
## Data Visualization
<|{dataset}|chart|mode=lines|x=Date|y[1]=MinTemp|y[2]=MaxTemp|color[1]=blue|color[2]=red|>
"""

section_3 = """
<|layout|columns= 1 5|
<|
## Custom Parameters
**Starting Date**\n\n<|{start_date}|date|not with_time|on_change=start_date_onchange|>

**Ending Date**\n\n<|{end_date}|date|not with_time|on_change=end_date_onchange|>

<|button|label=GO|on_action=button_action|>
|>
<|

Dataset
<|{DOWNLOAD_PATH}|file_download|on_action=download|>
    <|{dataset}|table|page_size=10|height=500px|width=65%|>

|>
|>
"""
def image_action(state):
    webbrowser.open("https://taipy.io")

def get_data(path: str):
    dataset = pd.read_csv(path)
    dataset["Date"] = pd.to_datetime(dataset["Date"]).dt.date
    return dataset

def start_date_onchange(state, var_name, value):
    state.start_date = value.date()

def end_date_onchange(state, var_name, value):
    state.end_date = value.date()

def filter_by_date_range(dataset, start_date, end_date):
    mask = (dataset['Date'] > start_date) & (dataset['Date'] <= end_date)
    return dataset.loc[mask]

def button_action(state):
    state.dataset = filter_by_date_range(dataset, state.start_date, state.end_date)
    notify(state, "info", "Updated date range from {} to {}.".format(state.start_date.strftime("%m/%d/%Y"), state.end_date.strftime("%m/%d/%Y")))

def download(state):
    state.dataset.to_csv(DOWNLOAD_PATH)

logo = "images/taipy_logo.jpg"
dataset = get_data("data/weather.csv")
start_date = datetime.date(2008, 12, 1)
end_date = datetime.date(2017, 6, 25)

gui = Gui(page=section_1+section_2+section_3)

if __name__ == '__main__':
    # the options in the gui.run() are optional, try without them
    gui.run(title='Taipy Demo GUI 2',
    		host='0.0.0.0',
    		port=os.environ.get('PORT', '5050'),
    		dark_mode=False)
else:
    app = gui.run(title='Taipy Demo GUI 2',
                  dark_mode=False,
                  run_server=False)


# Create app for demo-production-planning main.py
from pages.compare_cycles_md import *
import taipy as tp
import os
import pandas as pd
from taipy.gui import Gui

if __name__ == "__main__":
    tp.Core().run()

    if len(tp.get_scenarios())==0:
        cc_create_scenarios_for_cycle()

from pages.compare_scenario_md import *
from pages.databases_md import *
from pages.data_visualization_md import *
from pages.shared import *
from pages.scenario_manager.scenario_manager_md import *

def create_chart(sm_results: pd.DataFrame, var: str):
    """Functions that create/update the chart table visible in the "Databases" page. This
    function is used in the "on_change" function to change the chart when the graph selected is changed.

    Args:
        sm_results (pd.DataFrame): the results database that comes from the state
        var (str): the string that has to be found in the columns that are going to be used to create the chart table

    Returns:
        pd.DataFrame: the chart with the proper columns
    """
    if var == 'Cost':
        columns = ['index'] + [col for col in sm_results.columns if var in col]
    else:
        columns = ['index'] + [col for col in sm_results.columns if var in col and 'Cost' not in col]

    chart = sm_results[columns]
    return chart

def on_change(state, var_name, var_value):
    """This function is called whener a change in the state variables is done. When a change is seen, operations can be created
    depending on the variable changed
    Args:
        state (State): the state object of Taipy
        var_name (str): the changed variable name
        var_value (obj): the changed variable value
    """
    # if the changed variable is the scenario selected
    if var_name == "selected_scenario" and var_value:
        if state.selected_scenario.results.is_ready_for_reading:
            # I update all the other useful variables
            update_variables(state)

    if var_name == 'sm_graph_selected' or var_name == "selected_scenario":
        # Update the chart table
        str_to_select_chart = None
        chart_mapping = {
            'Costs': 'Cost',
            'Purchases': 'Purchase',
            'Productions': 'Production',
            'Stocks': 'Stock',
            'Back Order': 'BO',
            'Product FPA': 'FPA',
            'Product FPB': 'FPB',
            'Product RP1': 'RP1',
            'Product RP2': 'RP2'
        }

        str_to_select_chart = chart_mapping.get(state.sm_graph_selected)
        state.chart = create_chart(state.sm_results, str_to_select_chart)

        # If we are on the 'Databases' page, we have to create a temp CSV file
        if state.page == 'Databases':
            state.d_chart_csv_path = PATH_TO_TABLE
            state.chart.to_csv(state.d_chart_csv_path, sep=',')

def on_init(state):
    state.state_id = str(os.urandom(32))
    update_scenario_selector(state)

pages = {"/": Markdown('pages/shared.md'),
         "Data-Visualization":da_data_visualisation_md,
         "Scenario-Manager":sm_scenario_manager_md,
         "Compare-Scenarios":cs_compare_scenario_md,
         "Compare-Cycles":cc_compare_cycles_md,
         'Databases':da_databases_md
         }

if __name__ == "__main__":
    gui = Gui(pages=pages)
    gui.run(title="Production planning")


# Create app for demo-production-planning config.py
from taipy import Config, Frequency
import json
from algos.algos import *

# This code produces scenario_cfg and configures our graph of execution

###############################################################################
# Data nodes
###############################################################################

# we create our first datanode, the source is csv file
path_to_demand = 'data/time_series_demand.csv'
demand_cfg = Config.configure_data_node(id="demand",
                                storage_type="csv",
                                path=path_to_demand,
                                has_header=True)

with open('data/fixed_variables_default.json') as f:
    fixed_variables_default = json.load(f)

# creation of our second datanode that will have as a default data our fixed_variables_default
# this is this datanode that we will write on when we submit other values for fixed_variable
fixed_variables_cfg = Config.configure_data_node(id="fixed_variables", default_data = fixed_variables_default)

solver_name_cfg = Config.configure_data_node(id="solver_name", default_data="Default")

# here are the datanodes that keep track of the model : the model_created datanode, the model_solved datanode
model_created_cfg = Config.configure_data_node(id="model_created")
model_solved_cfg = Config.configure_data_node(id="model_solved")

# and this is the datanode that will be used to get our results from the main code
results_cfg = Config.configure_data_node(id="results")

###############################################################################
# Tasks
###############################################################################

# (demand_cfg,fixed_variables_cfg) -> |create_model| -> (model_created_cfg)
create_model_task = Config.configure_task(id="create_model",
                            input=[demand_cfg,fixed_variables_cfg],
                            function=create_model,
                            output=[model_created_cfg])

# (model_created_cfg, solver_name_cfg) -> |solve_model| -> (model_solved_cfg)
solve_model_cfg = Config.configure_task(id="solve_model",
                                    input=[model_created_cfg, solver_name_cfg],
                                    function=solve_model,
                                    output=[model_solved_cfg])

# (model_solved_cfg,fixed_variables_cfg,demand_cfg) -> |create_results| -> (results_cfg)
create_results_cfg = Config.configure_task(id="create_results",
                                       input=[model_solved_cfg,fixed_variables_cfg,demand_cfg],
                                       function=create_results,
                                       output=[results_cfg])

###############################################################################
# Scenario config
###############################################################################

scenario_cfg = Config.configure_scenario(id="scenario",task_configs=[create_model_task,solve_model_cfg,create_results_cfg], frequency=Frequency.MONTHLY)

Config.export("config/config.toml")