diff --git "a/pages/8_Scenario_Planner.py" "b/pages/8_Scenario_Planner.py"
--- "a/pages/8_Scenario_Planner.py"
+++ "b/pages/8_Scenario_Planner.py"
@@ -1,1551 +1,1551 @@
-import streamlit as st
-from numerize.numerize import numerize
-import numpy as np
-from functools import partial
-from collections import OrderedDict
-from plotly.subplots import make_subplots
-import plotly.graph_objects as go
-from utilities import (
- format_numbers,
- load_local_css,
- set_header,
- initialize_data,
- load_authenticator,
- send_email,
- channel_name_formating,
-)
-from classes import class_from_dict, class_to_dict
-import pickle
-import streamlit_authenticator as stauth
-import yaml
-from yaml import SafeLoader
-import re
-import pandas as pd
-import plotly.express as px
-
-
-st.set_page_config(layout="wide")
-load_local_css("styles.css")
-set_header()
-
-for k, v in st.session_state.items():
- if k not in ["logout", "login", "config"] and not k.startswith("FormSubmitter"):
- st.session_state[k] = v
-# ======================================================== #
-# ======================= Functions ====================== #
-# ======================================================== #
-
-
-def optimize(key, status_placeholder):
- """
- Optimize the spends for the sales
- """
-
- channel_list = [
- key for key, value in st.session_state["optimization_channels"].items() if value
- ]
-
- if len(channel_list) > 0:
- scenario = st.session_state["scenario"]
- if key.lower() == "media spends":
- with status_placeholder:
- with st.spinner("Optimizing"):
- result = st.session_state["scenario"].optimize(
- st.session_state["total_spends_change"], channel_list
- )
- # elif key.lower() == "revenue":
- else:
- with status_placeholder:
- with st.spinner("Optimizing"):
-
- result = st.session_state["scenario"].optimize_spends(
- st.session_state["total_sales_change"], channel_list
- )
- for channel_name, modified_spends in result:
-
- st.session_state[channel_name] = numerize(
- modified_spends * scenario.channels[channel_name].conversion_rate,
- 1,
- )
- prev_spends = (
- st.session_state["scenario"].channels[channel_name].actual_total_spends
- )
- st.session_state[f"{channel_name}_change"] = round(
- 100 * (modified_spends - prev_spends) / prev_spends, 2
- )
-
-
-def save_scenario(scenario_name):
- """
- Save the current scenario with the mentioned name in the session state
-
- Parameters
- ----------
- scenario_name
- Name of the scenario to be saved
- """
- if "saved_scenarios" not in st.session_state:
- st.session_state = OrderedDict()
-
- # st.session_state['saved_scenarios'][scenario_name] = st.session_state['scenario'].save()
- st.session_state["saved_scenarios"][scenario_name] = class_to_dict(
- st.session_state["scenario"]
- )
- st.session_state["scenario_input"] = ""
- # print(type(st.session_state['saved_scenarios']))
- with open("../saved_scenarios.pkl", "wb") as f:
- pickle.dump(st.session_state["saved_scenarios"], f)
-
-
-if "allow_spends_update" not in st.session_state:
- st.session_state["allow_spends_update"] = True
-
-if "allow_sales_update" not in st.session_state:
- st.session_state["allow_sales_update"] = True
-
-
-def update_sales_abs_slider():
- actual_sales = _scenario.actual_total_sales
- if validate_input(st.session_state["total_sales_change_abs_slider"]):
- modified_sales = extract_number_for_string(
- st.session_state["total_sales_change_abs_slider"]
- )
- st.session_state["total_sales_change"] = round(
- ((modified_sales / actual_sales) - 1) * 100
- )
- st.session_state["total_sales_change_abs"] = numerize(modified_sales, 1)
-
-
-def update_sales_abs():
- if (
- st.session_state["total_sales_change_abs"]
- in st.session_state["total_sales_change_abs_slider_options"]
- ):
- st.session_state["allow_sales_update"] = True
- else:
- st.session_state["allow_sales_update"] = False
-
- actual_sales = _scenario.actual_total_sales
- if (
- validate_input(st.session_state["total_sales_change_abs"])
- and st.session_state["allow_sales_update"]
- ):
- modified_sales = extract_number_for_string(
- st.session_state["total_sales_change_abs"]
- )
- st.session_state["total_sales_change"] = round(
- ((modified_sales / actual_sales) - 1) * 100
- )
- st.session_state["total_sales_change_abs_slider"] = numerize(modified_sales, 1)
-
-
-def update_sales():
- st.session_state["total_sales_change_abs"] = numerize(
- (1 + st.session_state["total_sales_change"] / 100)
- * _scenario.actual_total_sales,
- 1,
- )
- st.session_state["total_sales_change_abs_slider"] = numerize(
- (1 + st.session_state["total_sales_change"] / 100)
- * _scenario.actual_total_sales,
- 1,
- )
-
-
-def update_all_spends_abs_slider():
- actual_spends = _scenario.actual_total_spends
- if validate_input(st.session_state["total_spends_change_abs_slider"]):
- modified_spends = extract_number_for_string(
- st.session_state["total_spends_change_abs_slider"]
- )
- st.session_state["total_spends_change"] = round(
- ((modified_spends / actual_spends) - 1) * 100
- )
- st.session_state["total_spends_change_abs"] = numerize(modified_spends, 1)
-
- update_all_spends()
-
-
-# def update_all_spends_abs_slider():
-# actual_spends = _scenario.actual_total_spends
-# if validate_input(st.session_state["total_spends_change_abs_slider"]):
-# print("#" * 100)
-# print(st.session_state["total_spends_change_abs_slider"])
-# print("#" * 100)
-
-# modified_spends = extract_number_for_string(
-# st.session_state["total_spends_change_abs_slider"]
-# )
-# st.session_state["total_spends_change"] = (
-# (modified_spends / actual_spends) - 1
-# ) * 100
-# st.session_state["total_spends_change_abs"] = st.session_state[
-# "total_spends_change_abs_slider"
-# ]
-
-# update_all_spends()
-
-
-def update_all_spends_abs():
- if (
- st.session_state["total_spends_change_abs"]
- in st.session_state["total_spends_change_abs_slider_options"]
- ):
- st.session_state["allow_spends_update"] = True
- else:
- st.session_state["allow_spends_update"] = False
-
- actual_spends = _scenario.actual_total_spends
- if (
- validate_input(st.session_state["total_spends_change_abs"])
- and st.session_state["allow_spends_update"]
- ):
- modified_spends = extract_number_for_string(
- st.session_state["total_spends_change_abs"]
- )
- st.session_state["total_spends_change"] = (
- (modified_spends / actual_spends) - 1
- ) * 100
- st.session_state["total_spends_change_abs_slider"] = st.session_state[
- "total_spends_change_abs"
- ]
-
- update_all_spends()
-
-
-def update_spends():
- st.session_state["total_spends_change_abs"] = numerize(
- (1 + st.session_state["total_spends_change"] / 100)
- * _scenario.actual_total_spends,
- 1,
- )
- st.session_state["total_spends_change_abs_slider"] = numerize(
- (1 + st.session_state["total_spends_change"] / 100)
- * _scenario.actual_total_spends,
- 1,
- )
-
- update_all_spends()
-
-
-def update_all_spends():
- """
- Updates spends for all the channels with the given overall spends change
- """
- percent_change = st.session_state["total_spends_change"]
-
- for channel_name in st.session_state["channels_list"]:
- channel = st.session_state["scenario"].channels[channel_name]
- current_spends = channel.actual_total_spends
- modified_spends = (1 + percent_change / 100) * current_spends
- st.session_state["scenario"].update(channel_name, modified_spends)
- st.session_state[channel_name] = numerize(
- modified_spends * channel.conversion_rate, 1
- )
- st.session_state[f"{channel_name}_change"] = percent_change
-
-
-def extract_number_for_string(string_input):
- string_input = string_input.upper()
- if string_input.endswith("K"):
- return float(string_input[:-1]) * 10**3
- elif string_input.endswith("M"):
- return float(string_input[:-1]) * 10**6
- elif string_input.endswith("B"):
- return float(string_input[:-1]) * 10**9
-
-
-def validate_input(string_input):
- pattern = r"\d+\.?\d*[K|M|B]$"
- match = re.match(pattern, string_input)
- if match is None:
- return False
- return True
-
-
-def update_data_by_percent(channel_name):
- prev_spends = (
- st.session_state["scenario"].channels[channel_name].actual_total_spends
- * st.session_state["scenario"].channels[channel_name].conversion_rate
- )
- modified_spends = prev_spends * (
- 1 + st.session_state[f"{channel_name}_change"] / 100
- )
- st.session_state[channel_name] = numerize(modified_spends, 1)
- st.session_state["scenario"].update(
- channel_name,
- modified_spends
- / st.session_state["scenario"].channels[channel_name].conversion_rate,
- )
-
-
-def update_data(channel_name):
- """
- Updates the spends for the given channel
- """
-
- if validate_input(st.session_state[channel_name]):
- modified_spends = extract_number_for_string(st.session_state[channel_name])
- prev_spends = (
- st.session_state["scenario"].channels[channel_name].actual_total_spends
- * st.session_state["scenario"].channels[channel_name].conversion_rate
- )
- st.session_state[f"{channel_name}_change"] = round(
- 100 * (modified_spends - prev_spends) / prev_spends, 2
- )
- st.session_state["scenario"].update(
- channel_name,
- modified_spends
- / st.session_state["scenario"].channels[channel_name].conversion_rate,
- )
- # st.session_state['scenario'].update(channel_name, modified_spends)
- # else:
- # try:
- # modified_spends = float(st.session_state[channel_name])
- # prev_spends = st.session_state['scenario'].channels[channel_name].actual_total_spends * st.session_state['scenario'].channels[channel_name].conversion_rate
- # st.session_state[f'{channel_name}_change'] = round(100*(modified_spends - prev_spends) / prev_spends,2)
- # st.session_state['scenario'].update(channel_name, modified_spends/st.session_state['scenario'].channels[channel_name].conversion_rate)
- # st.session_state[f'{channel_name}'] = numerize(modified_spends,1)
- # except ValueError:
- # st.write('Invalid input')
-
-
-def select_channel_for_optimization(channel_name):
- """
- Marks the given channel for optimization
- """
- st.session_state["optimization_channels"][channel_name] = st.session_state[
- f"{channel_name}_selected"
- ]
-
-
-def select_all_channels_for_optimization():
- """
- Marks all the channel for optimization
- """
- for channel_name in st.session_state["optimization_channels"].keys():
- st.session_state[f"{channel_name}_selected"] = st.session_state[
- "optimze_all_channels"
- ]
- st.session_state["optimization_channels"][channel_name] = st.session_state[
- "optimze_all_channels"
- ]
-
-
-def update_penalty():
- """
- Updates the penalty flag for sales calculation
- """
- st.session_state["scenario"].update_penalty(st.session_state["apply_penalty"])
-
-
-def reset_scenario(panel_selected, file_selected, updated_rcs):
- # #print(st.session_state['default_scenario_dict'])
- # st.session_state['scenario'] = class_from_dict(st.session_state['default_scenario_dict'])
- # for channel in st.session_state['scenario'].channels.values():
- # st.session_state[channel.name] = float(channel.actual_total_spends * channel.conversion_rate)
- # initialize_data()
-
- if panel_selected == "Total Market":
- initialize_data(
- panel=panel_selected,
- target_file=file_selected,
- updated_rcs=updated_rcs,
- metrics=metrics_selected,
- )
- panel = None
- else:
- initialize_data(
- panel=panel_selected,
- target_file=file_selected,
- updated_rcs=updated_rcs,
- metrics=metrics_selected,
- )
-
- for channel_name in st.session_state["channels_list"]:
- st.session_state[f"{channel_name}_selected"] = False
- st.session_state[f"{channel_name}_change"] = 0
- st.session_state["optimze_all_channels"] = False
-
- st.session_state["total_sales_change"] = 0
-
- update_spends()
- update_sales()
-
- reset_inputs()
-
- # st.rerun()
-
-
-def format_number(num):
- if num >= 1_000_000:
- return f"{num / 1_000_000:.2f}M"
- elif num >= 1_000:
- return f"{num / 1_000:.0f}K"
- else:
- return f"{num:.2f}"
-
-
-def summary_plot(data, x, y, title, text_column):
- fig = px.bar(
- data,
- x=x,
- y=y,
- orientation="h",
- title=title,
- text=text_column,
- color="Channel_name",
- )
-
- # Convert text_column to numeric values
- data[text_column] = pd.to_numeric(data[text_column], errors="coerce")
-
- # Update the format of the displayed text based on magnitude
- fig.update_traces(
- texttemplate="%{text:.2s}",
- textposition="outside",
- hovertemplate="%{x:.2s}",
- )
-
- fig.update_layout(xaxis_title=x, yaxis_title="Channel Name", showlegend=False)
- return fig
-
-
-def s_curve(x, K, b, a, x0):
- return K / (1 + b * np.exp(-a * (x - x0)))
-
-
-def find_segment_value(x, roi, mroi):
- start_value = x[0]
- end_value = x[len(x) - 1]
-
- # Condition for green region: Both MROI and ROI > 1
- green_condition = (roi > 1) & (mroi > 1)
- left_indices = np.where(green_condition)[0]
- left_value = x[left_indices[0]] if left_indices.size > 0 else x[0]
-
- right_indices = np.where(green_condition)[0]
- right_value = x[right_indices[-1]] if right_indices.size > 0 else x[0]
-
- return start_value, end_value, left_value, right_value
-
-
-def calculate_rgba(
- start_value, end_value, left_value, right_value, current_channel_spends
-):
- # Initialize alpha to None for clarity
- alpha = None
-
- # Determine the color and calculate relative_position and alpha based on the point's position
- if start_value <= current_channel_spends <= left_value:
- color = "yellow"
- relative_position = (current_channel_spends - start_value) / (
- left_value - start_value
- )
- alpha = 0.8 - (0.6 * relative_position) # Alpha decreases from start to end
-
- elif left_value < current_channel_spends <= right_value:
- color = "green"
- relative_position = (current_channel_spends - left_value) / (
- right_value - left_value
- )
- alpha = 0.8 - (0.6 * relative_position) # Alpha decreases from start to end
-
- elif right_value < current_channel_spends <= end_value:
- color = "red"
- relative_position = (current_channel_spends - right_value) / (
- end_value - right_value
- )
- alpha = 0.2 + (0.6 * relative_position) # Alpha increases from start to end
-
- else:
- # Default case, if the spends are outside the defined ranges
- return "rgba(136, 136, 136, 0.5)" # Grey for values outside the range
-
- # Ensure alpha is within the intended range in case of any calculation overshoot
- alpha = max(0.2, min(alpha, 0.8))
-
- # Define color codes for RGBA
- color_codes = {
- "yellow": "255, 255, 0", # RGB for yellow
- "green": "0, 128, 0", # RGB for green
- "red": "255, 0, 0", # RGB for red
- }
-
- rgba = f"rgba({color_codes[color]}, {alpha})"
- return rgba
-
-
-def debug_temp(x_test, power, K, b, a, x0):
- print("*" * 100)
- # Calculate the count of bins
- count_lower_bin = sum(1 for x in x_test if x <= 2524)
- count_center_bin = sum(1 for x in x_test if x > 2524 and x <= 3377)
- count_ = sum(1 for x in x_test if x > 3377)
-
- print(
- f"""
- lower : {count_lower_bin}
- center : {count_center_bin}
- upper : {count_}
- """
- )
-
-
-# @st.cache
-def plot_response_curves():
- cols = 4
- rows = (
- len(channels_list) // cols
- if len(channels_list) % cols == 0
- else len(channels_list) // cols + 1
- )
- rcs = st.session_state["rcs"]
- shapes = []
- fig = make_subplots(rows=rows, cols=cols, subplot_titles=channels_list)
- for i in range(0, len(channels_list)):
- col = channels_list[i]
- x_actual = st.session_state["scenario"].channels[col].actual_spends
- # x_modified = st.session_state["scenario"].channels[col].modified_spends
-
- power = np.ceil(np.log(x_actual.max()) / np.log(10)) - 3
-
- K = rcs[col]["K"]
- b = rcs[col]["b"]
- a = rcs[col]["a"]
- x0 = rcs[col]["x0"]
-
- x_plot = np.linspace(0, 5 * x_actual.sum(), 50)
-
- x, y, marginal_roi = [], [], []
- for x_p in x_plot:
- x.append(x_p * x_actual / x_actual.sum())
-
- for index in range(len(x_plot)):
- y.append(s_curve(x[index] / 10**power, K, b, a, x0))
-
- for index in range(len(x_plot)):
- marginal_roi.append(
- a * y[index] * (1 - y[index] / np.maximum(K, np.finfo(float).eps))
- )
-
- x = (
- np.sum(x, axis=1)
- * st.session_state["scenario"].channels[col].conversion_rate
- )
- y = np.sum(y, axis=1)
- marginal_roi = (
- np.average(marginal_roi, axis=1)
- / st.session_state["scenario"].channels[col].conversion_rate
- )
-
- roi = y / np.maximum(x, np.finfo(float).eps)
-
- fig.add_trace(
- go.Scatter(
- x=x,
- y=y,
- name=col,
- customdata=np.stack((roi, marginal_roi), axis=-1),
- hovertemplate="Spend:%{x:$.2s}
Sale:%{y:$.2s}
ROI:%{customdata[0]:.3f}
MROI:%{customdata[1]:.3f}",
- line=dict(color="blue"),
- ),
- row=1 + (i) // cols,
- col=i % cols + 1,
- )
-
- x_optimal = (
- st.session_state["scenario"].channels[col].modified_total_spends
- * st.session_state["scenario"].channels[col].conversion_rate
- )
- y_optimal = st.session_state["scenario"].channels[col].modified_total_sales
-
- # if col == "Paid_social_others":
- # debug_temp(x_optimal * x_actual / x_actual.sum(), power, K, b, a, x0)
-
- fig.add_trace(
- go.Scatter(
- x=[x_optimal],
- y=[y_optimal],
- name=col,
- legendgroup=col,
- showlegend=False,
- marker=dict(color=["black"]),
- ),
- row=1 + (i) // cols,
- col=i % cols + 1,
- )
-
- shapes.append(
- go.layout.Shape(
- type="line",
- x0=0,
- y0=y_optimal,
- x1=x_optimal,
- y1=y_optimal,
- line_width=1,
- line_dash="dash",
- line_color="black",
- xref=f"x{i+1}",
- yref=f"y{i+1}",
- )
- )
-
- shapes.append(
- go.layout.Shape(
- type="line",
- x0=x_optimal,
- y0=0,
- x1=x_optimal,
- y1=y_optimal,
- line_width=1,
- line_dash="dash",
- line_color="black",
- xref=f"x{i+1}",
- yref=f"y{i+1}",
- )
- )
-
- start_value, end_value, left_value, right_value = find_segment_value(
- x,
- roi,
- marginal_roi,
- )
-
- # Adding background colors
- y_max = y.max() * 1.3 # 30% extra space above the max
-
- # Yellow region
- shapes.append(
- go.layout.Shape(
- type="rect",
- x0=start_value,
- y0=0,
- x1=left_value,
- y1=y_max,
- line=dict(width=0),
- fillcolor="rgba(255, 255, 0, 0.3)",
- layer="below",
- xref=f"x{i+1}",
- yref=f"y{i+1}",
- )
- )
-
- # Green region
- shapes.append(
- go.layout.Shape(
- type="rect",
- x0=left_value,
- y0=0,
- x1=right_value,
- y1=y_max,
- line=dict(width=0),
- fillcolor="rgba(0, 255, 0, 0.3)",
- layer="below",
- xref=f"x{i+1}",
- yref=f"y{i+1}",
- )
- )
-
- # Red region
- shapes.append(
- go.layout.Shape(
- type="rect",
- x0=right_value,
- y0=0,
- x1=end_value,
- y1=y_max,
- line=dict(width=0),
- fillcolor="rgba(255, 0, 0, 0.3)",
- layer="below",
- xref=f"x{i+1}",
- yref=f"y{i+1}",
- )
- )
-
- fig.update_layout(
- # height=1000,
- # width=1000,
- title_text=f"Response Curves (X: Spends Vs Y: {target})",
- showlegend=False,
- shapes=shapes,
- )
- fig.update_annotations(font_size=10)
- # fig.update_xaxes(title="Spends")
- # fig.update_yaxes(title=target)
- fig.update_yaxes(
- gridcolor="rgba(136, 136, 136, 0.5)", gridwidth=0.5, griddash="dash"
- )
-
- return fig
-
-
-# @st.cache
-# def plot_response_curves():
-# cols = 4
-# rcs = st.session_state["rcs"]
-# shapes = []
-# fig = make_subplots(rows=6, cols=cols, subplot_titles=channels_list)
-# for i in range(0, len(channels_list)):
-# col = channels_list[i]
-# x = st.session_state["actual_df"][col].values
-# spends = x.sum()
-# power = np.ceil(np.log(x.max()) / np.log(10)) - 3
-# x = np.linspace(0, 3 * x.max(), 200)
-
-# K = rcs[col]["K"]
-# b = rcs[col]["b"]
-# a = rcs[col]["a"]
-# x0 = rcs[col]["x0"]
-
-# y = s_curve(x / 10**power, K, b, a, x0)
-# roi = y / x
-# marginal_roi = a * (y) * (1 - y / K)
-# fig.add_trace(
-# go.Scatter(
-# x=52
-# * x
-# * st.session_state["scenario"].channels[col].conversion_rate,
-# y=52 * y,
-# name=col,
-# customdata=np.stack((roi, marginal_roi), axis=-1),
-# hovertemplate="Spend:%{x:$.2s}
Sale:%{y:$.2s}
ROI:%{customdata[0]:.3f}
MROI:%{customdata[1]:.3f}",
-# ),
-# row=1 + (i) // cols,
-# col=i % cols + 1,
-# )
-
-# fig.add_trace(
-# go.Scatter(
-# x=[
-# spends
-# * st.session_state["scenario"]
-# .channels[col]
-# .conversion_rate
-# ],
-# y=[52 * s_curve(spends / (10**power * 52), K, b, a, x0)],
-# name=col,
-# legendgroup=col,
-# showlegend=False,
-# marker=dict(color=["black"]),
-# ),
-# row=1 + (i) // cols,
-# col=i % cols + 1,
-# )
-
-# shapes.append(
-# go.layout.Shape(
-# type="line",
-# x0=0,
-# y0=52 * s_curve(spends / (10**power * 52), K, b, a, x0),
-# x1=spends
-# * st.session_state["scenario"].channels[col].conversion_rate,
-# y1=52 * s_curve(spends / (10**power * 52), K, b, a, x0),
-# line_width=1,
-# line_dash="dash",
-# line_color="black",
-# xref=f"x{i+1}",
-# yref=f"y{i+1}",
-# )
-# )
-
-# shapes.append(
-# go.layout.Shape(
-# type="line",
-# x0=spends
-# * st.session_state["scenario"].channels[col].conversion_rate,
-# y0=0,
-# x1=spends
-# * st.session_state["scenario"].channels[col].conversion_rate,
-# y1=52 * s_curve(spends / (10**power * 52), K, b, a, x0),
-# line_width=1,
-# line_dash="dash",
-# line_color="black",
-# xref=f"x{i+1}",
-# yref=f"y{i+1}",
-# )
-# )
-
-# fig.update_layout(
-# height=1500,
-# width=1000,
-# title_text="Response Curves",
-# showlegend=False,
-# shapes=shapes,
-# )
-# fig.update_annotations(font_size=10)
-# fig.update_xaxes(title="Spends")
-# fig.update_yaxes(title=target)
-# return fig
-
-
-# ======================================================== #
-# ==================== HTML Components =================== #
-# ======================================================== #
-
-
-def generate_spending_header(heading):
- return st.markdown(
- f"""
""", unsafe_allow_html=True
- )
-
-
-# ======================================================== #
-# =================== Session variables ================== #
-# ======================================================== #
-
-with open("config.yaml") as file:
- config = yaml.load(file, Loader=SafeLoader)
- st.session_state["config"] = config
-
-authenticator = stauth.Authenticate(
- config["credentials"],
- config["cookie"]["name"],
- config["cookie"]["key"],
- config["cookie"]["expiry_days"],
- config["preauthorized"],
-)
-st.session_state["authenticator"] = authenticator
-name, authentication_status, username = authenticator.login("Login", "main")
-auth_status = st.session_state.get("authentication_status")
-
-import os
-import glob
-
-
-def get_excel_names(directory):
- # Create a list to hold the final parts of the filenames
- last_portions = []
-
- # Patterns to match Excel files (.xlsx and .xls) that contain @#
- patterns = [
- os.path.join(directory, "*@#*.xlsx"),
- os.path.join(directory, "*@#*.xls"),
- ]
-
- # Process each pattern
- for pattern in patterns:
- files = glob.glob(pattern)
-
- # Extracting the last portion after @# for each file
- for file in files:
- base_name = os.path.basename(file)
- last_portion = base_name.split("@#")[-1]
- last_portion = last_portion.replace(".xlsx", "").replace(
- ".xls", ""
- ) # Removing extensions
- last_portions.append(last_portion)
-
- return last_portions
-
-
-def name_formating(channel_name):
- # Replace underscores with spaces
- name_mod = channel_name.replace("_", " ")
-
- # Capitalize the first letter of each word
- name_mod = name_mod.title()
-
- return name_mod
-
-
-@st.cache_resource(show_spinner=False)
-def panel_fetch(file_selected):
- raw_data_mmm_df = pd.read_excel(file_selected, sheet_name="RAW DATA MMM")
-
- if "Panel" in raw_data_mmm_df.columns:
- panel = list(set(raw_data_mmm_df["Panel"]))
- else:
- raw_data_mmm_df = None
- panel = None
-
- return panel
-
-
-def reset_inputs():
- if "total_spends_change_abs" in st.session_state:
- del st.session_state.total_spends_change_abs
- if "total_spends_change" in st.session_state:
- del st.session_state.total_spends_change
- if "total_spends_change_abs_slider" in st.session_state:
- del st.session_state.total_spends_change_abs_slider
-
- if "total_sales_change_abs" in st.session_state:
- del st.session_state.total_sales_change_abs
- if "total_sales_change" in st.session_state:
- del st.session_state.total_sales_change
- if "total_sales_change_abs_slider" in st.session_state:
- del st.session_state.total_sales_change_abs_slider
-
- st.session_state["initialized"] = False
-
-
-if auth_status == True:
- authenticator.logout("Logout", "main")
-
- st.header("Simulation")
- with st.expander('Optimized Spends Overview'):
- if st.button('Refresh'):
- st.rerun()
-
- import plotly.graph_objects as go
- from plotly.subplots import make_subplots
-
- # Define light colors for bars
- import plotly.graph_objects as go
- from plotly.subplots import make_subplots
-
- st.empty()
- #st.header('Model Result Analysis')
- spends_data=pd.read_excel('Overview_data_test.xlsx')
-
- with open('summary_df.pkl', 'rb') as file:
- summary_df_sorted = pickle.load(file)
- #st.write(summary_df_sorted)
-
- # selected_scenario= st.selectbox('Select Saved Scenarios',['S1','S2'])
- summary_df_sorted=summary_df_sorted.sort_values(by=['Optimized_spend'],ascending=False)
- summary_df_sorted['old_roi']=summary_df_sorted['Old_sales']/summary_df_sorted['Actual_spend']
- summary_df_sorted['new_roi']=summary_df_sorted['New_sales']/summary_df_sorted['Optimized_spend']
-
- total_actual_spend = summary_df_sorted['Actual_spend'].sum()
- total_optimized_spend = summary_df_sorted['Optimized_spend'].sum()
-
- actual_spend_percentage = (summary_df_sorted['Actual_spend'] / total_actual_spend) * 100
- optimized_spend_percentage = (summary_df_sorted['Optimized_spend'] / total_optimized_spend) * 100
-
-
-
- light_blue = 'rgba(0, 31, 120, 0.7)'
- light_orange = 'rgba(0, 181, 219, 0.7)'
- light_green = 'rgba(240, 61, 20, 0.7)'
- light_red = 'rgba(250, 110, 10, 0.7)'
- light_purple = 'rgba(255, 191, 69, 0.7)'
-
-
- # Create subplots with one row and two columns
- fig = make_subplots(rows=1, cols=3, subplot_titles=("Actual vs. Optimized Spend", "Actual vs. Optimized Contribution", "Actual vs. Optimized ROI"))
-
- # Add actual vs optimized spend bars
-
-
- fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['Actual_spend'], name='Actual',
- text=summary_df_sorted['Actual_spend'].apply(format_number) + ' '+' (' + actual_spend_percentage.round(2).astype(str) + '%)',
- marker_color=light_blue, orientation='h'),
- row=1,
- col=1)
-
-
- fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['Optimized_spend'], name='Optimized',
- text=summary_df_sorted['Optimized_spend'].apply(format_number) + ' (' + optimized_spend_percentage.round(2).astype(str) + '%)',
- marker_color=light_orange,
- orientation='h'),
- row=1,
- col=1)
-
- fig.update_xaxes(title_text="Amount", row=1, col=1)
-
- # Add actual vs optimized Contribution
- fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['New_sales'],
- name='Optimized Contribution',text=summary_df_sorted['New_sales'].apply(format_number),
- marker_color=light_orange, orientation='h',showlegend=False), row=1, col=2)
-
- fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['Old_sales'],
- name='Actual Contribution',text=summary_df_sorted['Old_sales'].apply(format_number),
- marker_color=light_blue, orientation='h',showlegend=False), row=1, col=2)
-
-
- fig.update_xaxes(title_text="Contribution", row=1, col=2)
-
- # Add actual vs optimized ROI bars
-
- fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['new_roi'],
- name='Optimized ROI',text=summary_df_sorted['new_roi'].apply(format_number) ,
- marker_color=light_orange, orientation='h',showlegend=False), row=1, col=3)
-
- fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['old_roi'],
- name='Actual ROI', text=summary_df_sorted['old_roi'].apply(format_number) ,
- marker_color=light_blue, orientation='h',showlegend=False), row=1, col=3)
-
- fig.update_xaxes(title_text="ROI", row=1, col=3)
-
- # Update layout
- fig.update_layout(title_text="Actual vs. Optimized Metrics for Media Channels",
- showlegend=True, yaxis=dict(title='Media Channels', autorange="reversed"))
-
- st.plotly_chart(fig,use_container_width=True)
-
- # fig = make_subplots(rows=1, cols=2, subplot_titles=("Actual Spend", "Optimized Spend"), specs=[[{'type': 'domain'}, {'type': 'domain'}]])
-
- # # Actual spend donut chart
- # fig.add_trace(go.Pie(labels=summary_df_sorted['Channel_name'],
- # values=summary_df_sorted['Actual_spend'], name='Actual Spend', hole=0.3,
- # marker_colors=[light_blue, light_orange, light_green, light_red, light_purple]), row=1, col=1)
-
- # # Optimized spend donut chart
- # fig.add_trace(go.Pie(labels=summary_df_sorted['Channel_name'],
- # values=summary_df_sorted['Optimized_spend'], name='Optimized Spend', hole=0.3,
- # arker_colors=[light_blue, light_orange, light_green, light_red, light_purple]), row=1, col=2)
-
- # # Update layout
- # fig.update_layout(title_text="Actual vs. Optimized Spend Distribution")
-
- # # Show plot
- # st.plotly_chart(fig, use_container_width=True)
-
- #col1, col2 = st.columns([1, 1])
-
- # Response Metrics
- directory = "metrics_level_data"
- metrics_list = get_excel_names(directory)
-
- # metrics_selected = col1.selectbox(
- # "Response Metrics",
- # metrics_list,
- # format_func=name_formating,
- # index=0,
- # on_change=reset_inputs,
- # )
-
- metrics_selected='Revenue'
- # Target
- target = name_formating(metrics_selected)
-
- file_selected = (
- f"Overview_data_test_panel@#{metrics_selected}.xlsx"
- )
-
- # Panel List
- panel_list = panel_fetch(file_selected)
-
- panel_list=[val for val in panel_list if str(val) !='nan']
-
- #st.write(panel_list)
- # Panel Selected
- panel_selected = st.selectbox(
- "Markets",
- ["Total Market"] + panel_list,
- index=0,
- on_change=reset_inputs,
- )
-
- st.session_state['selected_markets']=panel_selected
-
- if "update_rcs" in st.session_state:
- updated_rcs = st.session_state["update_rcs"]
- else:
- updated_rcs = None
-
- if "first_time" not in st.session_state:
- st.session_state["first_time"] = True
-
- # Check if state is initiaized
- is_state_initiaized = st.session_state.get("initialized", False)
- if not is_state_initiaized or st.session_state["first_time"]:
- # initialize_data()
- if panel_selected == "Total Market":
- initialize_data(
- panel=panel_selected,
- target_file=file_selected,
- updated_rcs=updated_rcs,
- metrics=metrics_selected,
- )
- panel = None
- else:
- initialize_data(
- panel=panel_selected,
- target_file=file_selected,
- updated_rcs=updated_rcs,
- metrics=metrics_selected,
- )
- st.session_state["initialized"] = True
- st.session_state["first_time"] = False
-
- # Channels List
- channels_list = st.session_state["channels_list"]
-
- # ======================================================== #
- # ========================== UI ========================== #
- # ======================================================== #
-
- # print(list(st.session_state.keys()))
- main_header = st.columns((2, 2))
- sub_header = st.columns((1, 1, 1, 1))
- _scenario = st.session_state["scenario"]
-
- if "total_spends_change" not in st.session_state:
- st.session_state.total_spends_change = 0
-
- if "total_sales_change" not in st.session_state:
- st.session_state.total_sales_change = 0
-
- if "total_spends_change_abs" not in st.session_state:
- st.session_state["total_spends_change_abs"] = numerize(
- _scenario.actual_total_spends, 1
- )
-
- if "total_sales_change_abs" not in st.session_state:
- st.session_state["total_sales_change_abs"] = numerize(
- _scenario.actual_total_sales, 1
- )
-
- if "total_spends_change_abs_slider" not in st.session_state:
- st.session_state.total_spends_change_abs_slider = numerize(
- _scenario.actual_total_spends, 1
- )
-
- if "total_sales_change_abs_slider" not in st.session_state:
- st.session_state.total_sales_change_abs_slider = numerize(
- _scenario.actual_total_sales, 1
- )
-
- with main_header[0]:
- st.subheader("Actual")
-
- with main_header[-1]:
- st.subheader("Simulated")
-
- with sub_header[0]:
- st.metric(label="Spends", value=format_numbers(_scenario.actual_total_spends))
-
- with sub_header[1]:
- st.metric(
- label=target,
- value=format_numbers(
- float(_scenario.actual_total_sales)
- ),
- )
-
- with sub_header[2]:
- st.metric(
- label="Spends",
- value=format_numbers(_scenario.modified_total_spends),
- delta=numerize(_scenario.delta_spends, 1),
- )
-
- with sub_header[3]:
- st.metric(
- label=target,
- value=format_numbers(
- float(_scenario.modified_total_sales)
- ),
- delta=numerize(_scenario.delta_sales, 1),
- )
-
- with st.expander("Channel Spends Simulator", expanded=True):
- _columns1 = st.columns((2, 2, 1, 1))
- with _columns1[0]:
- optimization_selection = st.selectbox(
- "Optimize", options=["Media Spends", target], key="optimization_key"
- )
-
- with _columns1[1]:
- st.markdown("#")
- # if st.checkbox(
- # label="Optimize all Channels",
- # key="optimze_all_channels",
- # value=False,
- # # on_change=select_all_channels_for_optimization,
- # ):
- # select_all_channels_for_optimization()
-
- st.checkbox(
- label="Optimize all Channels",
- key="optimze_all_channels",
- value=False,
- on_change=select_all_channels_for_optimization,
- )
-
- with _columns1[2]:
- st.markdown("#")
- # st.button(
- # "Optimize",
- # on_click=optimize,
- # args=(st.session_state["optimization_key"]),
- # use_container_width=True,
- # )
-
- optimize_placeholder = st.empty()
-
- with _columns1[3]:
- st.markdown("#")
- st.button(
- "Reset",
- on_click=reset_scenario,
- args=(panel_selected, file_selected, updated_rcs),
- use_container_width=True,
- )
-
- _columns2 = st.columns((2, 2, 2))
- if st.session_state["optimization_key"] == "Media Spends":
- with _columns2[0]:
- spend_input = st.text_input(
- "Absolute",
- key="total_spends_change_abs",
- # label_visibility="collapsed",
- on_change=update_all_spends_abs,
- )
-
- with _columns2[1]:
- st.number_input(
- "Percent Change",
- key="total_spends_change",
- min_value=-50,
- max_value=50,
- step=1,
- on_change=update_spends,
- )
-
- with _columns2[2]:
- min_value = round(_scenario.actual_total_spends * 0.5)
- max_value = round(_scenario.actual_total_spends * 1.5)
- st.session_state["total_spends_change_abs_slider_options"] = [
- numerize(value, 1)
- for value in range(min_value, max_value + 1, int(1e4))
- ]
-
- # st.select_slider(
- # "Absolute Slider",
- # options=st.session_state["total_spends_change_abs_slider_options"],
- # key="total_spends_change_abs_slider",
- # on_change=update_all_spends_abs_slider,
- # )
-
- elif st.session_state["optimization_key"] == target:
- with _columns2[0]:
- sales_input = st.text_input(
- "Absolute",
- key="total_sales_change_abs",
- on_change=update_sales_abs,
- )
-
- with _columns2[1]:
- st.number_input(
- "Percent Change",
- key="total_sales_change",
- min_value=-50,
- max_value=50,
- step=1,
- on_change=update_sales,
- )
- with _columns2[2]:
- min_value = round(_scenario.actual_total_sales * 0.5)
- max_value = round(_scenario.actual_total_sales * 1.5)
- st.session_state["total_sales_change_abs_slider_options"] = [
- numerize(value, 1)
- for value in range(min_value, max_value + 1, int(1e5))
- ]
-
- st.select_slider(
- "Absolute Slider",
- options=st.session_state["total_sales_change_abs_slider_options"],
- key="total_sales_change_abs_slider",
- on_change=update_sales_abs_slider,
- )
-
- if (
- not st.session_state["allow_sales_update"]
- and optimization_selection == target
- ):
- st.warning("Invalid Input")
-
- if (
- not st.session_state["allow_spends_update"]
- and optimization_selection == "Media Spends"
- ):
- st.warning("Invalid Input")
-
- status_placeholder = st.empty()
-
- # if optimize_placeholder.button("Optimize", use_container_width=True):
- # optimize(st.session_state["optimization_key"], status_placeholder)
- # st.rerun()
-
- optimize_placeholder.button(
- "Optimize",
- on_click=optimize,
- args=(st.session_state["optimization_key"], status_placeholder),
- use_container_width=True,
- )
-
- st.markdown("""
""", unsafe_allow_html=True)
- _columns = st.columns((2.5, 2, 1.5, 1.5, 1))
- with _columns[0]:
- generate_spending_header("Channel")
- with _columns[1]:
- generate_spending_header("Spends Input")
- with _columns[2]:
- generate_spending_header("Spends")
- with _columns[3]:
- generate_spending_header(target)
- with _columns[4]:
- generate_spending_header("Optimize")
-
- st.markdown("""
""", unsafe_allow_html=True)
-
- if "acutual_predicted" not in st.session_state:
- st.session_state["acutual_predicted"] = {
- "Channel_name": [],
- "Actual_spend": [],
- "Optimized_spend": [],
- "Delta": [],
- "New_sales":[],
- "Old_sales":[]
- }
- for i, channel_name in enumerate(channels_list):
- _channel_class = st.session_state["scenario"].channels[channel_name]
- _columns = st.columns((2.5, 1.5, 1.5, 1.5, 1))
- with _columns[0]:
- st.write(channel_name_formating(channel_name))
- bin_placeholder = st.container()
-
- with _columns[1]:
- channel_bounds = _channel_class.bounds
- channel_spends = float(_channel_class.actual_total_spends)
- min_value = float((1 + channel_bounds[0] / 100) * channel_spends)
- max_value = float((1 + channel_bounds[1] / 100) * channel_spends)
- ##print(st.session_state[channel_name])
- spend_input = st.text_input(
- channel_name,
- key=channel_name,
- label_visibility="collapsed",
- on_change=partial(update_data, channel_name),
- )
- if not validate_input(spend_input):
- st.error("Invalid input")
-
- channel_name_current = f"{channel_name}_change"
-
- st.number_input(
- "Percent Change",
- key=channel_name_current,
- step=1,
- on_change=partial(update_data_by_percent, channel_name),
- )
-
- with _columns[2]:
- # spends
- current_channel_spends = float(
- _channel_class.modified_total_spends
- * _channel_class.conversion_rate
- )
- actual_channel_spends = float(
- _channel_class.actual_total_spends * _channel_class.conversion_rate
- )
- spends_delta = float(
- _channel_class.delta_spends * _channel_class.conversion_rate
- )
- st.session_state["acutual_predicted"]["Channel_name"].append(
- channel_name
- )
- st.session_state["acutual_predicted"]["Actual_spend"].append(
- actual_channel_spends
- )
- st.session_state["acutual_predicted"]["Optimized_spend"].append(
- current_channel_spends
- )
- st.session_state["acutual_predicted"]["Delta"].append(spends_delta)
- ## REMOVE
- st.metric(
- "Spends",
- format_numbers(current_channel_spends),
- delta=numerize(spends_delta, 1),
- label_visibility="collapsed",
- )
-
- with _columns[3]:
- # sales
- current_channel_sales = float(_channel_class.modified_total_sales)
- actual_channel_sales = float(_channel_class.actual_total_sales)
- sales_delta = float(_channel_class.delta_sales)
- st.session_state["acutual_predicted"]["Old_sales"].append(actual_channel_sales)
- st.session_state["acutual_predicted"]["New_sales"].append(current_channel_sales)
- #st.write(actual_channel_sales)
-
- st.metric(
- target,
- format_numbers(current_channel_sales, include_indicator=False),
- delta=numerize(sales_delta, 1),
- label_visibility="collapsed",
- )
-
- with _columns[4]:
-
- # if st.checkbox(
- # label="select for optimization",
- # key=f"{channel_name}_selected",
- # value=False,
- # # on_change=partial(select_channel_for_optimization, channel_name),
- # label_visibility="collapsed",
- # ):
- # select_channel_for_optimization(channel_name)
-
- st.checkbox(
- label="select for optimization",
- key=f"{channel_name}_selected",
- value=False,
- on_change=partial(select_channel_for_optimization, channel_name),
- label_visibility="collapsed",
- )
-
- st.markdown(
- """
""",
- unsafe_allow_html=True,
- )
-
- # Bins
- col = channels_list[i]
- x_actual = st.session_state["scenario"].channels[col].actual_spends
- x_modified = st.session_state["scenario"].channels[col].modified_spends
-
- x_total = x_modified.sum()
- power = np.ceil(np.log(x_actual.max()) / np.log(10)) - 3
-
- updated_rcs_key = f"{metrics_selected}#@{panel_selected}#@{channel_name}"
-
- if updated_rcs and updated_rcs_key in list(updated_rcs.keys()):
- K = updated_rcs[updated_rcs_key]["K"]
- b = updated_rcs[updated_rcs_key]["b"]
- a = updated_rcs[updated_rcs_key]["a"]
- x0 = updated_rcs[updated_rcs_key]["x0"]
- else:
- K = st.session_state["rcs"][col]["K"]
- b = st.session_state["rcs"][col]["b"]
- a = st.session_state["rcs"][col]["a"]
- x0 = st.session_state["rcs"][col]["x0"]
-
- x_plot = np.linspace(0, 5 * x_actual.sum(), 200)
-
- # Append current_channel_spends to the end of x_plot
- x_plot = np.append(x_plot, current_channel_spends)
-
- x, y, marginal_roi = [], [], []
- for x_p in x_plot:
- x.append(x_p * x_actual / x_actual.sum())
-
- for index in range(len(x_plot)):
- y.append(s_curve(x[index] / 10**power, K, b, a, x0))
-
- for index in range(len(x_plot)):
- marginal_roi.append(
- a * y[index] * (1 - y[index] / np.maximum(K, np.finfo(float).eps))
- )
-
- x = (
- np.sum(x, axis=1)
- * st.session_state["scenario"].channels[col].conversion_rate
- )
- y = np.sum(y, axis=1)
- marginal_roi = (
- np.average(marginal_roi, axis=1)
- / st.session_state["scenario"].channels[col].conversion_rate
- )
-
- roi = y / np.maximum(x, np.finfo(float).eps)
-
- #st.write(roi[-1])
-
- roi_current, marginal_roi_current = roi[-1], marginal_roi[-1]
- x, y, roi, marginal_roi = (
- x[:-1],
- y[:-1],
- roi[:-1],
- marginal_roi[:-1],
- ) # Drop data for current spends
-
- start_value, end_value, left_value, right_value = find_segment_value(
- x,
- roi,
- marginal_roi,
- )
-
- #st.write(roi_current)
-
- rgba = calculate_rgba(
- start_value,
- end_value,
- left_value,
- right_value,
- current_channel_spends,
- )
-
- with bin_placeholder:
- st.markdown(
- f"""
-
-
ROI: {round(roi_current,1)}
-
Marginal ROI: {round(marginal_roi_current,1)}
-
- """,
- unsafe_allow_html=True,
- )
-
- with st.expander("See Response Curves", expanded=True):
- fig = plot_response_curves()
- st.plotly_chart(fig, use_container_width=True)
-
- _columns = st.columns(2)
- # with _columns[0]:
- st.subheader("Save Scenario")
- scenario_name = st.text_input(
- "Scenario name",
- key="scenario_input",
- placeholder="Scenario name",
- label_visibility="collapsed",
- )
- st.button(
- "Save",
- on_click=lambda: save_scenario(scenario_name),
- disabled=len(st.session_state["scenario_input"]) == 0,use_container_width=True
- )
-
- summary_df = pd.DataFrame(st.session_state["acutual_predicted"])
- summary_df.drop_duplicates(subset="Channel_name", keep="last", inplace=True)
-
- summary_df_sorted = summary_df.sort_values(by="Delta", ascending=False)
- summary_df_sorted["Delta_percent"] = np.round(
- ((summary_df_sorted["Optimized_spend"] / summary_df_sorted["Actual_spend"]) - 1)
- * 100,
- 2,
- )
-
- with open("summary_df.pkl", "wb") as f:
- pickle.dump(summary_df_sorted, f)
- # st.dataframe(summary_df_sorted)
- # ___columns=st.columns(3)
- # with ___columns[2]:
- # fig=summary_plot(summary_df_sorted, x='Delta_percent', y='Channel_name', title='Delta', text_column='Delta_percent')
- # st.plotly_chart(fig,use_container_width=True)
- # with ___columns[0]:
- # fig=summary_plot(summary_df_sorted, x='Actual_spend', y='Channel_name', title='Actual Spend', text_column='Actual_spend')
- # st.plotly_chart(fig,use_container_width=True)
- # with ___columns[1]:
- # fig=summary_plot(summary_df_sorted, x='Optimized_spend', y='Channel_name', title='Planned Spend', text_column='Optimized_spend')
- # st.plotly_chart(fig,use_container_width=True)
-
-
-elif auth_status == False:
- st.error("Username/Password is incorrect")
-
-if auth_status != True:
- try:
- username_forgot_pw, email_forgot_password, random_password = (
- authenticator.forgot_password("Forgot password")
- )
- if username_forgot_pw:
- st.session_state["config"]["credentials"]["usernames"][username_forgot_pw][
- "password"
- ] = stauth.Hasher([random_password]).generate()[0]
- send_email(email_forgot_password, random_password)
- st.success("New password sent securely")
- # Random password to be transferred to user securely
- elif username_forgot_pw == False:
- st.error("Username not found")
- except Exception as e:
- st.error(e)
+import streamlit as st
+from numerize.numerize import numerize
+import numpy as np
+from functools import partial
+from collections import OrderedDict
+from plotly.subplots import make_subplots
+import plotly.graph_objects as go
+from utilities import (
+ format_numbers,
+ load_local_css,
+ set_header,
+ initialize_data,
+ load_authenticator,
+ send_email,
+ channel_name_formating,
+)
+from classes import class_from_dict, class_to_dict
+import pickle
+import streamlit_authenticator as stauth
+import yaml
+from yaml import SafeLoader
+import re
+import pandas as pd
+import plotly.express as px
+
+
+st.set_page_config(layout="wide")
+load_local_css("styles.css")
+set_header()
+
+for k, v in st.session_state.items():
+ if k not in ["logout", "login", "config"] and not k.startswith("FormSubmitter"):
+ st.session_state[k] = v
+# ======================================================== #
+# ======================= Functions ====================== #
+# ======================================================== #
+
+
+def optimize(key, status_placeholder):
+ """
+ Optimize the spends for the sales
+ """
+
+ channel_list = [
+ key for key, value in st.session_state["optimization_channels"].items() if value
+ ]
+
+ if len(channel_list) > 0:
+ scenario = st.session_state["scenario"]
+ if key.lower() == "media spends":
+ with status_placeholder:
+ with st.spinner("Optimizing"):
+ result = st.session_state["scenario"].optimize(
+ st.session_state["total_spends_change"], channel_list
+ )
+ # elif key.lower() == "revenue":
+ else:
+ with status_placeholder:
+ with st.spinner("Optimizing"):
+
+ result = st.session_state["scenario"].optimize_spends(
+ st.session_state["total_sales_change"], channel_list
+ )
+ for channel_name, modified_spends in result:
+
+ st.session_state[channel_name] = numerize(
+ modified_spends * scenario.channels[channel_name].conversion_rate,
+ 1,
+ )
+ prev_spends = (
+ st.session_state["scenario"].channels[channel_name].actual_total_spends
+ )
+ st.session_state[f"{channel_name}_change"] = round(
+ 100 * (modified_spends - prev_spends) / prev_spends, 2
+ )
+
+
+def save_scenario(scenario_name):
+ """
+ Save the current scenario with the mentioned name in the session state
+
+ Parameters
+ ----------
+ scenario_name
+ Name of the scenario to be saved
+ """
+ if "saved_scenarios" not in st.session_state:
+ st.session_state = OrderedDict()
+
+ # st.session_state['saved_scenarios'][scenario_name] = st.session_state['scenario'].save()
+ st.session_state["saved_scenarios"][scenario_name] = class_to_dict(
+ st.session_state["scenario"]
+ )
+ st.session_state["scenario_input"] = ""
+ # print(type(st.session_state['saved_scenarios']))
+ with open("../saved_scenarios.pkl", "wb") as f:
+ pickle.dump(st.session_state["saved_scenarios"], f)
+
+
+if "allow_spends_update" not in st.session_state:
+ st.session_state["allow_spends_update"] = True
+
+if "allow_sales_update" not in st.session_state:
+ st.session_state["allow_sales_update"] = True
+
+
+def update_sales_abs_slider():
+ actual_sales = _scenario.actual_total_sales
+ if validate_input(st.session_state["total_sales_change_abs_slider"]):
+ modified_sales = extract_number_for_string(
+ st.session_state["total_sales_change_abs_slider"]
+ )
+ st.session_state["total_sales_change"] = round(
+ ((modified_sales / actual_sales) - 1) * 100
+ )
+ st.session_state["total_sales_change_abs"] = numerize(modified_sales, 1)
+
+
+def update_sales_abs():
+ if (
+ st.session_state["total_sales_change_abs"]
+ in st.session_state["total_sales_change_abs_slider_options"]
+ ):
+ st.session_state["allow_sales_update"] = True
+ else:
+ st.session_state["allow_sales_update"] = False
+
+ actual_sales = _scenario.actual_total_sales
+ if (
+ validate_input(st.session_state["total_sales_change_abs"])
+ and st.session_state["allow_sales_update"]
+ ):
+ modified_sales = extract_number_for_string(
+ st.session_state["total_sales_change_abs"]
+ )
+ st.session_state["total_sales_change"] = round(
+ ((modified_sales / actual_sales) - 1) * 100
+ )
+ st.session_state["total_sales_change_abs_slider"] = numerize(modified_sales, 1)
+
+
+def update_sales():
+ st.session_state["total_sales_change_abs"] = numerize(
+ (1 + st.session_state["total_sales_change"] / 100)
+ * _scenario.actual_total_sales,
+ 1,
+ )
+ st.session_state["total_sales_change_abs_slider"] = numerize(
+ (1 + st.session_state["total_sales_change"] / 100)
+ * _scenario.actual_total_sales,
+ 1,
+ )
+
+
+def update_all_spends_abs_slider():
+ actual_spends = _scenario.actual_total_spends
+ if validate_input(st.session_state["total_spends_change_abs_slider"]):
+ modified_spends = extract_number_for_string(
+ st.session_state["total_spends_change_abs_slider"]
+ )
+ st.session_state["total_spends_change"] = round(
+ ((modified_spends / actual_spends) - 1) * 100
+ )
+ st.session_state["total_spends_change_abs"] = numerize(modified_spends, 1)
+
+ update_all_spends()
+
+
+# def update_all_spends_abs_slider():
+# actual_spends = _scenario.actual_total_spends
+# if validate_input(st.session_state["total_spends_change_abs_slider"]):
+# print("#" * 100)
+# print(st.session_state["total_spends_change_abs_slider"])
+# print("#" * 100)
+
+# modified_spends = extract_number_for_string(
+# st.session_state["total_spends_change_abs_slider"]
+# )
+# st.session_state["total_spends_change"] = (
+# (modified_spends / actual_spends) - 1
+# ) * 100
+# st.session_state["total_spends_change_abs"] = st.session_state[
+# "total_spends_change_abs_slider"
+# ]
+
+# update_all_spends()
+
+
+def update_all_spends_abs():
+ if (
+ st.session_state["total_spends_change_abs"]
+ in st.session_state["total_spends_change_abs_slider_options"]
+ ):
+ st.session_state["allow_spends_update"] = True
+ else:
+ st.session_state["allow_spends_update"] = False
+
+ actual_spends = _scenario.actual_total_spends
+ if (
+ validate_input(st.session_state["total_spends_change_abs"])
+ and st.session_state["allow_spends_update"]
+ ):
+ modified_spends = extract_number_for_string(
+ st.session_state["total_spends_change_abs"]
+ )
+ st.session_state["total_spends_change"] = (
+ (modified_spends / actual_spends) - 1
+ ) * 100
+ st.session_state["total_spends_change_abs_slider"] = st.session_state[
+ "total_spends_change_abs"
+ ]
+
+ update_all_spends()
+
+
+def update_spends():
+ st.session_state["total_spends_change_abs"] = numerize(
+ (1 + st.session_state["total_spends_change"] / 100)
+ * _scenario.actual_total_spends,
+ 1,
+ )
+ st.session_state["total_spends_change_abs_slider"] = numerize(
+ (1 + st.session_state["total_spends_change"] / 100)
+ * _scenario.actual_total_spends,
+ 1,
+ )
+
+ update_all_spends()
+
+
+def update_all_spends():
+ """
+ Updates spends for all the channels with the given overall spends change
+ """
+ percent_change = st.session_state["total_spends_change"]
+
+ for channel_name in st.session_state["channels_list"]:
+ channel = st.session_state["scenario"].channels[channel_name]
+ current_spends = channel.actual_total_spends
+ modified_spends = (1 + percent_change / 100) * current_spends
+ st.session_state["scenario"].update(channel_name, modified_spends)
+ st.session_state[channel_name] = numerize(
+ modified_spends * channel.conversion_rate, 1
+ )
+ st.session_state[f"{channel_name}_change"] = percent_change
+
+
+def extract_number_for_string(string_input):
+ string_input = string_input.upper()
+ if string_input.endswith("K"):
+ return float(string_input[:-1]) * 10**3
+ elif string_input.endswith("M"):
+ return float(string_input[:-1]) * 10**6
+ elif string_input.endswith("B"):
+ return float(string_input[:-1]) * 10**9
+
+
+def validate_input(string_input):
+ pattern = r"\d+\.?\d*[K|M|B]$"
+ match = re.match(pattern, string_input)
+ if match is None:
+ return False
+ return True
+
+
+def update_data_by_percent(channel_name):
+ prev_spends = (
+ st.session_state["scenario"].channels[channel_name].actual_total_spends
+ * st.session_state["scenario"].channels[channel_name].conversion_rate
+ )
+ modified_spends = prev_spends * (
+ 1 + st.session_state[f"{channel_name}_change"] / 100
+ )
+ st.session_state[channel_name] = numerize(modified_spends, 1)
+ st.session_state["scenario"].update(
+ channel_name,
+ modified_spends
+ / st.session_state["scenario"].channels[channel_name].conversion_rate,
+ )
+
+
+def update_data(channel_name):
+ """
+ Updates the spends for the given channel
+ """
+
+ if validate_input(st.session_state[channel_name]):
+ modified_spends = extract_number_for_string(st.session_state[channel_name])
+ prev_spends = (
+ st.session_state["scenario"].channels[channel_name].actual_total_spends
+ * st.session_state["scenario"].channels[channel_name].conversion_rate
+ )
+ st.session_state[f"{channel_name}_change"] = round(
+ 100 * (modified_spends - prev_spends) / prev_spends, 2
+ )
+ st.session_state["scenario"].update(
+ channel_name,
+ modified_spends
+ / st.session_state["scenario"].channels[channel_name].conversion_rate,
+ )
+ # st.session_state['scenario'].update(channel_name, modified_spends)
+ # else:
+ # try:
+ # modified_spends = float(st.session_state[channel_name])
+ # prev_spends = st.session_state['scenario'].channels[channel_name].actual_total_spends * st.session_state['scenario'].channels[channel_name].conversion_rate
+ # st.session_state[f'{channel_name}_change'] = round(100*(modified_spends - prev_spends) / prev_spends,2)
+ # st.session_state['scenario'].update(channel_name, modified_spends/st.session_state['scenario'].channels[channel_name].conversion_rate)
+ # st.session_state[f'{channel_name}'] = numerize(modified_spends,1)
+ # except ValueError:
+ # st.write('Invalid input')
+
+
+def select_channel_for_optimization(channel_name):
+ """
+ Marks the given channel for optimization
+ """
+ st.session_state["optimization_channels"][channel_name] = st.session_state[
+ f"{channel_name}_selected"
+ ]
+
+
+def select_all_channels_for_optimization():
+ """
+ Marks all the channel for optimization
+ """
+ for channel_name in st.session_state["optimization_channels"].keys():
+ st.session_state[f"{channel_name}_selected"] = st.session_state[
+ "optimze_all_channels"
+ ]
+ st.session_state["optimization_channels"][channel_name] = st.session_state[
+ "optimze_all_channels"
+ ]
+
+
+def update_penalty():
+ """
+ Updates the penalty flag for sales calculation
+ """
+ st.session_state["scenario"].update_penalty(st.session_state["apply_penalty"])
+
+
+def reset_scenario(panel_selected, file_selected, updated_rcs):
+ # #print(st.session_state['default_scenario_dict'])
+ # st.session_state['scenario'] = class_from_dict(st.session_state['default_scenario_dict'])
+ # for channel in st.session_state['scenario'].channels.values():
+ # st.session_state[channel.name] = float(channel.actual_total_spends * channel.conversion_rate)
+ # initialize_data()
+
+ if panel_selected == "Total Market":
+ initialize_data(
+ panel=panel_selected,
+ target_file=file_selected,
+ updated_rcs=updated_rcs,
+ metrics=metrics_selected,
+ )
+ panel = None
+ else:
+ initialize_data(
+ panel=panel_selected,
+ target_file=file_selected,
+ updated_rcs=updated_rcs,
+ metrics=metrics_selected,
+ )
+
+ for channel_name in st.session_state["channels_list"]:
+ st.session_state[f"{channel_name}_selected"] = False
+ st.session_state[f"{channel_name}_change"] = 0
+ st.session_state["optimze_all_channels"] = False
+
+ st.session_state["total_sales_change"] = 0
+
+ update_spends()
+ update_sales()
+
+ reset_inputs()
+
+ # st.rerun()
+
+
+def format_number(num):
+ if num >= 1_000_000:
+ return f"{num / 1_000_000:.2f}M"
+ elif num >= 1_000:
+ return f"{num / 1_000:.0f}K"
+ else:
+ return f"{num:.2f}"
+
+
+def summary_plot(data, x, y, title, text_column):
+ fig = px.bar(
+ data,
+ x=x,
+ y=y,
+ orientation="h",
+ title=title,
+ text=text_column,
+ color="Channel_name",
+ )
+
+ # Convert text_column to numeric values
+ data[text_column] = pd.to_numeric(data[text_column], errors="coerce")
+
+ # Update the format of the displayed text based on magnitude
+ fig.update_traces(
+ texttemplate="%{text:.2s}",
+ textposition="outside",
+ hovertemplate="%{x:.2s}",
+ )
+
+ fig.update_layout(xaxis_title=x, yaxis_title="Channel Name", showlegend=False)
+ return fig
+
+
+def s_curve(x, K, b, a, x0):
+ return K / (1 + b * np.exp(-a * (x - x0)))
+
+
+def find_segment_value(x, roi, mroi):
+ start_value = x[0]
+ end_value = x[len(x) - 1]
+
+ # Condition for green region: Both MROI and ROI > 1
+ green_condition = (roi > 1) & (mroi > 1)
+ left_indices = np.where(green_condition)[0]
+ left_value = x[left_indices[0]] if left_indices.size > 0 else x[0]
+
+ right_indices = np.where(green_condition)[0]
+ right_value = x[right_indices[-1]] if right_indices.size > 0 else x[0]
+
+ return start_value, end_value, left_value, right_value
+
+
+def calculate_rgba(
+ start_value, end_value, left_value, right_value, current_channel_spends
+):
+ # Initialize alpha to None for clarity
+ alpha = None
+
+ # Determine the color and calculate relative_position and alpha based on the point's position
+ if start_value <= current_channel_spends <= left_value:
+ color = "yellow"
+ relative_position = (current_channel_spends - start_value) / (
+ left_value - start_value
+ )
+ alpha = 0.8 - (0.6 * relative_position) # Alpha decreases from start to end
+
+ elif left_value < current_channel_spends <= right_value:
+ color = "green"
+ relative_position = (current_channel_spends - left_value) / (
+ right_value - left_value
+ )
+ alpha = 0.8 - (0.6 * relative_position) # Alpha decreases from start to end
+
+ elif right_value < current_channel_spends <= end_value:
+ color = "red"
+ relative_position = (current_channel_spends - right_value) / (
+ end_value - right_value
+ )
+ alpha = 0.2 + (0.6 * relative_position) # Alpha increases from start to end
+
+ else:
+ # Default case, if the spends are outside the defined ranges
+ return "rgba(136, 136, 136, 0.5)" # Grey for values outside the range
+
+ # Ensure alpha is within the intended range in case of any calculation overshoot
+ alpha = max(0.2, min(alpha, 0.8))
+
+ # Define color codes for RGBA
+ color_codes = {
+ "yellow": "255, 255, 0", # RGB for yellow
+ "green": "0, 128, 0", # RGB for green
+ "red": "255, 0, 0", # RGB for red
+ }
+
+ rgba = f"rgba({color_codes[color]}, {alpha})"
+ return rgba
+
+
+def debug_temp(x_test, power, K, b, a, x0):
+ print("*" * 100)
+ # Calculate the count of bins
+ count_lower_bin = sum(1 for x in x_test if x <= 2524)
+ count_center_bin = sum(1 for x in x_test if x > 2524 and x <= 3377)
+ count_ = sum(1 for x in x_test if x > 3377)
+
+ print(
+ f"""
+ lower : {count_lower_bin}
+ center : {count_center_bin}
+ upper : {count_}
+ """
+ )
+
+
+# @st.cache
+def plot_response_curves():
+ cols = 4
+ rows = (
+ len(channels_list) // cols
+ if len(channels_list) % cols == 0
+ else len(channels_list) // cols + 1
+ )
+ rcs = st.session_state["rcs"]
+ shapes = []
+ fig = make_subplots(rows=rows, cols=cols, subplot_titles=channels_list)
+ for i in range(0, len(channels_list)):
+ col = channels_list[i]
+ x_actual = st.session_state["scenario"].channels[col].actual_spends
+ # x_modified = st.session_state["scenario"].channels[col].modified_spends
+
+ power = np.ceil(np.log(x_actual.max()) / np.log(10)) - 3
+
+ K = rcs[col]["K"]
+ b = rcs[col]["b"]
+ a = rcs[col]["a"]
+ x0 = rcs[col]["x0"]
+
+ x_plot = np.linspace(0, 5 * x_actual.sum(), 50)
+
+ x, y, marginal_roi = [], [], []
+ for x_p in x_plot:
+ x.append(x_p * x_actual / x_actual.sum())
+
+ for index in range(len(x_plot)):
+ y.append(s_curve(x[index] / 10**power, K, b, a, x0))
+
+ for index in range(len(x_plot)):
+ marginal_roi.append(
+ a * y[index] * (1 - y[index] / np.maximum(K, np.finfo(float).eps))
+ )
+
+ x = (
+ np.sum(x, axis=1)
+ * st.session_state["scenario"].channels[col].conversion_rate
+ )
+ y = np.sum(y, axis=1)
+ marginal_roi = (
+ np.average(marginal_roi, axis=1)
+ / st.session_state["scenario"].channels[col].conversion_rate
+ )
+
+ roi = y / np.maximum(x, np.finfo(float).eps)
+
+ fig.add_trace(
+ go.Scatter(
+ x=x,
+ y=y,
+ name=col,
+ customdata=np.stack((roi, marginal_roi), axis=-1),
+ hovertemplate="Spend:%{x:$.2s}
Sale:%{y:$.2s}
ROI:%{customdata[0]:.3f}
MROI:%{customdata[1]:.3f}",
+ line=dict(color="blue"),
+ ),
+ row=1 + (i) // cols,
+ col=i % cols + 1,
+ )
+
+ x_optimal = (
+ st.session_state["scenario"].channels[col].modified_total_spends
+ * st.session_state["scenario"].channels[col].conversion_rate
+ )
+ y_optimal = st.session_state["scenario"].channels[col].modified_total_sales
+
+ # if col == "Paid_social_others":
+ # debug_temp(x_optimal * x_actual / x_actual.sum(), power, K, b, a, x0)
+
+ fig.add_trace(
+ go.Scatter(
+ x=[x_optimal],
+ y=[y_optimal],
+ name=col,
+ legendgroup=col,
+ showlegend=False,
+ marker=dict(color=["black"]),
+ ),
+ row=1 + (i) // cols,
+ col=i % cols + 1,
+ )
+
+ shapes.append(
+ go.layout.Shape(
+ type="line",
+ x0=0,
+ y0=y_optimal,
+ x1=x_optimal,
+ y1=y_optimal,
+ line_width=1,
+ line_dash="dash",
+ line_color="black",
+ xref=f"x{i+1}",
+ yref=f"y{i+1}",
+ )
+ )
+
+ shapes.append(
+ go.layout.Shape(
+ type="line",
+ x0=x_optimal,
+ y0=0,
+ x1=x_optimal,
+ y1=y_optimal,
+ line_width=1,
+ line_dash="dash",
+ line_color="black",
+ xref=f"x{i+1}",
+ yref=f"y{i+1}",
+ )
+ )
+
+ start_value, end_value, left_value, right_value = find_segment_value(
+ x,
+ roi,
+ marginal_roi,
+ )
+
+ # Adding background colors
+ y_max = y.max() * 1.3 # 30% extra space above the max
+
+ # Yellow region
+ shapes.append(
+ go.layout.Shape(
+ type="rect",
+ x0=start_value,
+ y0=0,
+ x1=left_value,
+ y1=y_max,
+ line=dict(width=0),
+ fillcolor="rgba(255, 255, 0, 0.3)",
+ layer="below",
+ xref=f"x{i+1}",
+ yref=f"y{i+1}",
+ )
+ )
+
+ # Green region
+ shapes.append(
+ go.layout.Shape(
+ type="rect",
+ x0=left_value,
+ y0=0,
+ x1=right_value,
+ y1=y_max,
+ line=dict(width=0),
+ fillcolor="rgba(0, 255, 0, 0.3)",
+ layer="below",
+ xref=f"x{i+1}",
+ yref=f"y{i+1}",
+ )
+ )
+
+ # Red region
+ shapes.append(
+ go.layout.Shape(
+ type="rect",
+ x0=right_value,
+ y0=0,
+ x1=end_value,
+ y1=y_max,
+ line=dict(width=0),
+ fillcolor="rgba(255, 0, 0, 0.3)",
+ layer="below",
+ xref=f"x{i+1}",
+ yref=f"y{i+1}",
+ )
+ )
+
+ fig.update_layout(
+ # height=1000,
+ # width=1000,
+ title_text=f"Response Curves (X: Spends Vs Y: {target})",
+ showlegend=False,
+ shapes=shapes,
+ )
+ fig.update_annotations(font_size=10)
+ # fig.update_xaxes(title="Spends")
+ # fig.update_yaxes(title=target)
+ fig.update_yaxes(
+ gridcolor="rgba(136, 136, 136, 0.5)", gridwidth=0.5, griddash="dash"
+ )
+
+ return fig
+
+
+# @st.cache
+# def plot_response_curves():
+# cols = 4
+# rcs = st.session_state["rcs"]
+# shapes = []
+# fig = make_subplots(rows=6, cols=cols, subplot_titles=channels_list)
+# for i in range(0, len(channels_list)):
+# col = channels_list[i]
+# x = st.session_state["actual_df"][col].values
+# spends = x.sum()
+# power = np.ceil(np.log(x.max()) / np.log(10)) - 3
+# x = np.linspace(0, 3 * x.max(), 200)
+
+# K = rcs[col]["K"]
+# b = rcs[col]["b"]
+# a = rcs[col]["a"]
+# x0 = rcs[col]["x0"]
+
+# y = s_curve(x / 10**power, K, b, a, x0)
+# roi = y / x
+# marginal_roi = a * (y) * (1 - y / K)
+# fig.add_trace(
+# go.Scatter(
+# x=52
+# * x
+# * st.session_state["scenario"].channels[col].conversion_rate,
+# y=52 * y,
+# name=col,
+# customdata=np.stack((roi, marginal_roi), axis=-1),
+# hovertemplate="Spend:%{x:$.2s}
Sale:%{y:$.2s}
ROI:%{customdata[0]:.3f}
MROI:%{customdata[1]:.3f}",
+# ),
+# row=1 + (i) // cols,
+# col=i % cols + 1,
+# )
+
+# fig.add_trace(
+# go.Scatter(
+# x=[
+# spends
+# * st.session_state["scenario"]
+# .channels[col]
+# .conversion_rate
+# ],
+# y=[52 * s_curve(spends / (10**power * 52), K, b, a, x0)],
+# name=col,
+# legendgroup=col,
+# showlegend=False,
+# marker=dict(color=["black"]),
+# ),
+# row=1 + (i) // cols,
+# col=i % cols + 1,
+# )
+
+# shapes.append(
+# go.layout.Shape(
+# type="line",
+# x0=0,
+# y0=52 * s_curve(spends / (10**power * 52), K, b, a, x0),
+# x1=spends
+# * st.session_state["scenario"].channels[col].conversion_rate,
+# y1=52 * s_curve(spends / (10**power * 52), K, b, a, x0),
+# line_width=1,
+# line_dash="dash",
+# line_color="black",
+# xref=f"x{i+1}",
+# yref=f"y{i+1}",
+# )
+# )
+
+# shapes.append(
+# go.layout.Shape(
+# type="line",
+# x0=spends
+# * st.session_state["scenario"].channels[col].conversion_rate,
+# y0=0,
+# x1=spends
+# * st.session_state["scenario"].channels[col].conversion_rate,
+# y1=52 * s_curve(spends / (10**power * 52), K, b, a, x0),
+# line_width=1,
+# line_dash="dash",
+# line_color="black",
+# xref=f"x{i+1}",
+# yref=f"y{i+1}",
+# )
+# )
+
+# fig.update_layout(
+# height=1500,
+# width=1000,
+# title_text="Response Curves",
+# showlegend=False,
+# shapes=shapes,
+# )
+# fig.update_annotations(font_size=10)
+# fig.update_xaxes(title="Spends")
+# fig.update_yaxes(title=target)
+# return fig
+
+
+# ======================================================== #
+# ==================== HTML Components =================== #
+# ======================================================== #
+
+
+def generate_spending_header(heading):
+ return st.markdown(
+ f"""""", unsafe_allow_html=True
+ )
+
+
+# ======================================================== #
+# =================== Session variables ================== #
+# ======================================================== #
+
+with open("config.yaml") as file:
+ config = yaml.load(file, Loader=SafeLoader)
+ st.session_state["config"] = config
+
+authenticator = stauth.Authenticate(
+ config["credentials"],
+ config["cookie"]["name"],
+ config["cookie"]["key"],
+ config["cookie"]["expiry_days"],
+ config["preauthorized"],
+)
+st.session_state["authenticator"] = authenticator
+name, authentication_status, username = authenticator.login("Login", "main")
+auth_status = st.session_state.get("authentication_status")
+
+import os
+import glob
+
+
+def get_excel_names(directory):
+ # Create a list to hold the final parts of the filenames
+ last_portions = []
+
+ # Patterns to match Excel files (.xlsx and .xls) that contain @#
+ patterns = [
+ os.path.join(directory, "*@#*.xlsx"),
+ os.path.join(directory, "*@#*.xls"),
+ ]
+
+ # Process each pattern
+ for pattern in patterns:
+ files = glob.glob(pattern)
+
+ # Extracting the last portion after @# for each file
+ for file in files:
+ base_name = os.path.basename(file)
+ last_portion = base_name.split("@#")[-1]
+ last_portion = last_portion.replace(".xlsx", "").replace(
+ ".xls", ""
+ ) # Removing extensions
+ last_portions.append(last_portion)
+
+ return last_portions
+
+
+def name_formating(channel_name):
+ # Replace underscores with spaces
+ name_mod = channel_name.replace("_", " ")
+
+ # Capitalize the first letter of each word
+ name_mod = name_mod.title()
+
+ return name_mod
+
+
+@st.cache_resource(show_spinner=False)
+def panel_fetch(file_selected):
+ raw_data_mmm_df = pd.read_excel(file_selected, sheet_name="RAW DATA MMM")
+
+ if "Panel" in raw_data_mmm_df.columns:
+ panel = list(set(raw_data_mmm_df["Panel"]))
+ else:
+ raw_data_mmm_df = None
+ panel = None
+
+ return panel
+
+
+def reset_inputs():
+ if "total_spends_change_abs" in st.session_state:
+ del st.session_state.total_spends_change_abs
+ if "total_spends_change" in st.session_state:
+ del st.session_state.total_spends_change
+ if "total_spends_change_abs_slider" in st.session_state:
+ del st.session_state.total_spends_change_abs_slider
+
+ if "total_sales_change_abs" in st.session_state:
+ del st.session_state.total_sales_change_abs
+ if "total_sales_change" in st.session_state:
+ del st.session_state.total_sales_change
+ if "total_sales_change_abs_slider" in st.session_state:
+ del st.session_state.total_sales_change_abs_slider
+
+ st.session_state["initialized"] = False
+
+
+if auth_status == True:
+ authenticator.logout("Logout", "main")
+
+ st.header("Simulation")
+ with st.expander('Optimized Spends Overview'):
+ if st.button('Refresh'):
+ st.rerun()
+
+ import plotly.graph_objects as go
+ from plotly.subplots import make_subplots
+
+ # Define light colors for bars
+ import plotly.graph_objects as go
+ from plotly.subplots import make_subplots
+
+ st.empty()
+ #st.header('Model Result Analysis')
+ spends_data=pd.read_excel('Overview_data_test.xlsx')
+
+ with open('summary_df.pkl', 'rb') as file:
+ summary_df_sorted = pickle.load(file)
+ #st.write(summary_df_sorted)
+
+ # selected_scenario= st.selectbox('Select Saved Scenarios',['S1','S2'])
+ summary_df_sorted=summary_df_sorted.sort_values(by=['Optimized_spend'],ascending=False)
+ summary_df_sorted['old_roi']=summary_df_sorted['Old_sales']/summary_df_sorted['Actual_spend']
+ summary_df_sorted['new_roi']=summary_df_sorted['New_sales']/summary_df_sorted['Optimized_spend']
+
+ total_actual_spend = summary_df_sorted['Actual_spend'].sum()
+ total_optimized_spend = summary_df_sorted['Optimized_spend'].sum()
+
+ actual_spend_percentage = (summary_df_sorted['Actual_spend'] / total_actual_spend) * 100
+ optimized_spend_percentage = (summary_df_sorted['Optimized_spend'] / total_optimized_spend) * 100
+
+
+
+ light_blue = 'rgba(0, 31, 120, 0.7)'
+ light_orange = 'rgba(0, 181, 219, 0.7)'
+ light_green = 'rgba(240, 61, 20, 0.7)'
+ light_red = 'rgba(250, 110, 10, 0.7)'
+ light_purple = 'rgba(255, 191, 69, 0.7)'
+
+
+ # Create subplots with one row and two columns
+ fig = make_subplots(rows=1, cols=3, subplot_titles=("Actual vs. Optimized Spend", "Actual vs. Optimized Contribution", "Actual vs. Optimized ROI"))
+
+ # Add actual vs optimized spend bars
+
+
+ fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['Actual_spend'], name='Actual',
+ text=summary_df_sorted['Actual_spend'].apply(format_number) + ' '+' (' + actual_spend_percentage.round(2).astype(str) + '%)',
+ marker_color=light_blue, orientation='h'),
+ row=1,
+ col=1)
+
+
+ fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['Optimized_spend'], name='Optimized',
+ text=summary_df_sorted['Optimized_spend'].apply(format_number) + ' (' + optimized_spend_percentage.round(2).astype(str) + '%)',
+ marker_color=light_orange,
+ orientation='h'),
+ row=1,
+ col=1)
+
+ fig.update_xaxes(title_text="Amount", row=1, col=1)
+
+ # Add actual vs optimized Contribution
+ fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['New_sales'],
+ name='Optimized Contribution',text=summary_df_sorted['New_sales'].apply(format_number),
+ marker_color=light_orange, orientation='h',showlegend=False), row=1, col=2)
+
+ fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['Old_sales'],
+ name='Actual Contribution',text=summary_df_sorted['Old_sales'].apply(format_number),
+ marker_color=light_blue, orientation='h',showlegend=False), row=1, col=2)
+
+
+ fig.update_xaxes(title_text="Contribution", row=1, col=2)
+
+ # Add actual vs optimized ROI bars
+
+ fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['new_roi'],
+ name='Optimized ROI',text=summary_df_sorted['new_roi'].apply(format_number) ,
+ marker_color=light_orange, orientation='h',showlegend=False), row=1, col=3)
+
+ fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['old_roi'],
+ name='Actual ROI', text=summary_df_sorted['old_roi'].apply(format_number) ,
+ marker_color=light_blue, orientation='h',showlegend=False), row=1, col=3)
+
+ fig.update_xaxes(title_text="ROI", row=1, col=3)
+
+ # Update layout
+ fig.update_layout(title_text="Actual vs. Optimized Metrics for Media Channels",
+ showlegend=True, yaxis=dict(title='Media Channels', autorange="reversed"))
+
+ st.plotly_chart(fig,use_container_width=True)
+
+ # fig = make_subplots(rows=1, cols=2, subplot_titles=("Actual Spend", "Optimized Spend"), specs=[[{'type': 'domain'}, {'type': 'domain'}]])
+
+ # # Actual spend donut chart
+ # fig.add_trace(go.Pie(labels=summary_df_sorted['Channel_name'],
+ # values=summary_df_sorted['Actual_spend'], name='Actual Spend', hole=0.3,
+ # marker_colors=[light_blue, light_orange, light_green, light_red, light_purple]), row=1, col=1)
+
+ # # Optimized spend donut chart
+ # fig.add_trace(go.Pie(labels=summary_df_sorted['Channel_name'],
+ # values=summary_df_sorted['Optimized_spend'], name='Optimized Spend', hole=0.3,
+ # arker_colors=[light_blue, light_orange, light_green, light_red, light_purple]), row=1, col=2)
+
+ # # Update layout
+ # fig.update_layout(title_text="Actual vs. Optimized Spend Distribution")
+
+ # # Show plot
+ # st.plotly_chart(fig, use_container_width=True)
+
+ #col1, col2 = st.columns([1, 1])
+
+ # Response Metrics
+ directory = "metrics_level_data"
+ metrics_list = get_excel_names(directory)
+
+ # metrics_selected = col1.selectbox(
+ # "Response Metrics",
+ # metrics_list,
+ # format_func=name_formating,
+ # index=0,
+ # on_change=reset_inputs,
+ # )
+
+ metrics_selected='revenue'
+ # Target
+ target = name_formating(metrics_selected)
+
+ file_selected = (
+ f"Overview_data_test_panel@#{metrics_selected}.xlsx"
+ )
+
+ # Panel List
+ panel_list = panel_fetch(file_selected)
+
+ panel_list=[val for val in panel_list if str(val) !='nan']
+
+ #st.write(panel_list)
+ # Panel Selected
+ panel_selected = st.selectbox(
+ "Markets",
+ ["Total Market"] + panel_list,
+ index=0,
+ on_change=reset_inputs,
+ )
+
+ st.session_state['selected_markets']=panel_selected
+
+ if "update_rcs" in st.session_state:
+ updated_rcs = st.session_state["update_rcs"]
+ else:
+ updated_rcs = None
+
+ if "first_time" not in st.session_state:
+ st.session_state["first_time"] = True
+
+ # Check if state is initiaized
+ is_state_initiaized = st.session_state.get("initialized", False)
+ if not is_state_initiaized or st.session_state["first_time"]:
+ # initialize_data()
+ if panel_selected == "Total Market":
+ initialize_data(
+ panel=panel_selected,
+ target_file=file_selected,
+ updated_rcs=updated_rcs,
+ metrics=metrics_selected,
+ )
+ panel = None
+ else:
+ initialize_data(
+ panel=panel_selected,
+ target_file=file_selected,
+ updated_rcs=updated_rcs,
+ metrics=metrics_selected,
+ )
+ st.session_state["initialized"] = True
+ st.session_state["first_time"] = False
+
+ # Channels List
+ channels_list = st.session_state["channels_list"]
+
+ # ======================================================== #
+ # ========================== UI ========================== #
+ # ======================================================== #
+
+ # print(list(st.session_state.keys()))
+ main_header = st.columns((2, 2))
+ sub_header = st.columns((1, 1, 1, 1))
+ _scenario = st.session_state["scenario"]
+
+ if "total_spends_change" not in st.session_state:
+ st.session_state.total_spends_change = 0
+
+ if "total_sales_change" not in st.session_state:
+ st.session_state.total_sales_change = 0
+
+ if "total_spends_change_abs" not in st.session_state:
+ st.session_state["total_spends_change_abs"] = numerize(
+ _scenario.actual_total_spends, 1
+ )
+
+ if "total_sales_change_abs" not in st.session_state:
+ st.session_state["total_sales_change_abs"] = numerize(
+ _scenario.actual_total_sales, 1
+ )
+
+ if "total_spends_change_abs_slider" not in st.session_state:
+ st.session_state.total_spends_change_abs_slider = numerize(
+ _scenario.actual_total_spends, 1
+ )
+
+ if "total_sales_change_abs_slider" not in st.session_state:
+ st.session_state.total_sales_change_abs_slider = numerize(
+ _scenario.actual_total_sales, 1
+ )
+
+ with main_header[0]:
+ st.subheader("Actual")
+
+ with main_header[-1]:
+ st.subheader("Simulated")
+
+ with sub_header[0]:
+ st.metric(label="Spends", value=format_numbers(_scenario.actual_total_spends))
+
+ with sub_header[1]:
+ st.metric(
+ label=target,
+ value=format_numbers(
+ float(_scenario.actual_total_sales)
+ ),
+ )
+
+ with sub_header[2]:
+ st.metric(
+ label="Spends",
+ value=format_numbers(_scenario.modified_total_spends),
+ delta=numerize(_scenario.delta_spends, 1),
+ )
+
+ with sub_header[3]:
+ st.metric(
+ label=target,
+ value=format_numbers(
+ float(_scenario.modified_total_sales)
+ ),
+ delta=numerize(_scenario.delta_sales, 1),
+ )
+
+ with st.expander("Channel Spends Simulator", expanded=True):
+ _columns1 = st.columns((2, 2, 1, 1))
+ with _columns1[0]:
+ optimization_selection = st.selectbox(
+ "Optimize", options=["Media Spends", target], key="optimization_key"
+ )
+
+ with _columns1[1]:
+ st.markdown("#")
+ # if st.checkbox(
+ # label="Optimize all Channels",
+ # key="optimze_all_channels",
+ # value=False,
+ # # on_change=select_all_channels_for_optimization,
+ # ):
+ # select_all_channels_for_optimization()
+
+ st.checkbox(
+ label="Optimize all Channels",
+ key="optimze_all_channels",
+ value=False,
+ on_change=select_all_channels_for_optimization,
+ )
+
+ with _columns1[2]:
+ st.markdown("#")
+ # st.button(
+ # "Optimize",
+ # on_click=optimize,
+ # args=(st.session_state["optimization_key"]),
+ # use_container_width=True,
+ # )
+
+ optimize_placeholder = st.empty()
+
+ with _columns1[3]:
+ st.markdown("#")
+ st.button(
+ "Reset",
+ on_click=reset_scenario,
+ args=(panel_selected, file_selected, updated_rcs),
+ use_container_width=True,
+ )
+
+ _columns2 = st.columns((2, 2, 2))
+ if st.session_state["optimization_key"] == "Media Spends":
+ with _columns2[0]:
+ spend_input = st.text_input(
+ "Absolute",
+ key="total_spends_change_abs",
+ # label_visibility="collapsed",
+ on_change=update_all_spends_abs,
+ )
+
+ with _columns2[1]:
+ st.number_input(
+ "Percent Change",
+ key="total_spends_change",
+ min_value=-50,
+ max_value=50,
+ step=1,
+ on_change=update_spends,
+ )
+
+ with _columns2[2]:
+ min_value = round(_scenario.actual_total_spends * 0.5)
+ max_value = round(_scenario.actual_total_spends * 1.5)
+ st.session_state["total_spends_change_abs_slider_options"] = [
+ numerize(value, 1)
+ for value in range(min_value, max_value + 1, int(1e4))
+ ]
+
+ # st.select_slider(
+ # "Absolute Slider",
+ # options=st.session_state["total_spends_change_abs_slider_options"],
+ # key="total_spends_change_abs_slider",
+ # on_change=update_all_spends_abs_slider,
+ # )
+
+ elif st.session_state["optimization_key"] == target:
+ with _columns2[0]:
+ sales_input = st.text_input(
+ "Absolute",
+ key="total_sales_change_abs",
+ on_change=update_sales_abs,
+ )
+
+ with _columns2[1]:
+ st.number_input(
+ "Percent Change",
+ key="total_sales_change",
+ min_value=-50,
+ max_value=50,
+ step=1,
+ on_change=update_sales,
+ )
+ with _columns2[2]:
+ min_value = round(_scenario.actual_total_sales * 0.5)
+ max_value = round(_scenario.actual_total_sales * 1.5)
+ st.session_state["total_sales_change_abs_slider_options"] = [
+ numerize(value, 1)
+ for value in range(min_value, max_value + 1, int(1e5))
+ ]
+
+ st.select_slider(
+ "Absolute Slider",
+ options=st.session_state["total_sales_change_abs_slider_options"],
+ key="total_sales_change_abs_slider",
+ on_change=update_sales_abs_slider,
+ )
+
+ if (
+ not st.session_state["allow_sales_update"]
+ and optimization_selection == target
+ ):
+ st.warning("Invalid Input")
+
+ if (
+ not st.session_state["allow_spends_update"]
+ and optimization_selection == "Media Spends"
+ ):
+ st.warning("Invalid Input")
+
+ status_placeholder = st.empty()
+
+ # if optimize_placeholder.button("Optimize", use_container_width=True):
+ # optimize(st.session_state["optimization_key"], status_placeholder)
+ # st.rerun()
+
+ optimize_placeholder.button(
+ "Optimize",
+ on_click=optimize,
+ args=(st.session_state["optimization_key"], status_placeholder),
+ use_container_width=True,
+ )
+
+ st.markdown("""
""", unsafe_allow_html=True)
+ _columns = st.columns((2.5, 2, 1.5, 1.5, 1))
+ with _columns[0]:
+ generate_spending_header("Channel")
+ with _columns[1]:
+ generate_spending_header("Spends Input")
+ with _columns[2]:
+ generate_spending_header("Spends")
+ with _columns[3]:
+ generate_spending_header(target)
+ with _columns[4]:
+ generate_spending_header("Optimize")
+
+ st.markdown("""
""", unsafe_allow_html=True)
+
+ if "acutual_predicted" not in st.session_state:
+ st.session_state["acutual_predicted"] = {
+ "Channel_name": [],
+ "Actual_spend": [],
+ "Optimized_spend": [],
+ "Delta": [],
+ "New_sales":[],
+ "Old_sales":[]
+ }
+ for i, channel_name in enumerate(channels_list):
+ _channel_class = st.session_state["scenario"].channels[channel_name]
+ _columns = st.columns((2.5, 1.5, 1.5, 1.5, 1))
+ with _columns[0]:
+ st.write(channel_name_formating(channel_name))
+ bin_placeholder = st.container()
+
+ with _columns[1]:
+ channel_bounds = _channel_class.bounds
+ channel_spends = float(_channel_class.actual_total_spends)
+ min_value = float((1 + channel_bounds[0] / 100) * channel_spends)
+ max_value = float((1 + channel_bounds[1] / 100) * channel_spends)
+ ##print(st.session_state[channel_name])
+ spend_input = st.text_input(
+ channel_name,
+ key=channel_name,
+ label_visibility="collapsed",
+ on_change=partial(update_data, channel_name),
+ )
+ if not validate_input(spend_input):
+ st.error("Invalid input")
+
+ channel_name_current = f"{channel_name}_change"
+
+ st.number_input(
+ "Percent Change",
+ key=channel_name_current,
+ step=1,
+ on_change=partial(update_data_by_percent, channel_name),
+ )
+
+ with _columns[2]:
+ # spends
+ current_channel_spends = float(
+ _channel_class.modified_total_spends
+ * _channel_class.conversion_rate
+ )
+ actual_channel_spends = float(
+ _channel_class.actual_total_spends * _channel_class.conversion_rate
+ )
+ spends_delta = float(
+ _channel_class.delta_spends * _channel_class.conversion_rate
+ )
+ st.session_state["acutual_predicted"]["Channel_name"].append(
+ channel_name
+ )
+ st.session_state["acutual_predicted"]["Actual_spend"].append(
+ actual_channel_spends
+ )
+ st.session_state["acutual_predicted"]["Optimized_spend"].append(
+ current_channel_spends
+ )
+ st.session_state["acutual_predicted"]["Delta"].append(spends_delta)
+ ## REMOVE
+ st.metric(
+ "Spends",
+ format_numbers(current_channel_spends),
+ delta=numerize(spends_delta, 1),
+ label_visibility="collapsed",
+ )
+
+ with _columns[3]:
+ # sales
+ current_channel_sales = float(_channel_class.modified_total_sales)
+ actual_channel_sales = float(_channel_class.actual_total_sales)
+ sales_delta = float(_channel_class.delta_sales)
+ st.session_state["acutual_predicted"]["Old_sales"].append(actual_channel_sales)
+ st.session_state["acutual_predicted"]["New_sales"].append(current_channel_sales)
+ #st.write(actual_channel_sales)
+
+ st.metric(
+ target,
+ format_numbers(current_channel_sales, include_indicator=False),
+ delta=numerize(sales_delta, 1),
+ label_visibility="collapsed",
+ )
+
+ with _columns[4]:
+
+ # if st.checkbox(
+ # label="select for optimization",
+ # key=f"{channel_name}_selected",
+ # value=False,
+ # # on_change=partial(select_channel_for_optimization, channel_name),
+ # label_visibility="collapsed",
+ # ):
+ # select_channel_for_optimization(channel_name)
+
+ st.checkbox(
+ label="select for optimization",
+ key=f"{channel_name}_selected",
+ value=False,
+ on_change=partial(select_channel_for_optimization, channel_name),
+ label_visibility="collapsed",
+ )
+
+ st.markdown(
+ """
""",
+ unsafe_allow_html=True,
+ )
+
+ # Bins
+ col = channels_list[i]
+ x_actual = st.session_state["scenario"].channels[col].actual_spends
+ x_modified = st.session_state["scenario"].channels[col].modified_spends
+
+ x_total = x_modified.sum()
+ power = np.ceil(np.log(x_actual.max()) / np.log(10)) - 3
+
+ updated_rcs_key = f"{metrics_selected}#@{panel_selected}#@{channel_name}"
+
+ if updated_rcs and updated_rcs_key in list(updated_rcs.keys()):
+ K = updated_rcs[updated_rcs_key]["K"]
+ b = updated_rcs[updated_rcs_key]["b"]
+ a = updated_rcs[updated_rcs_key]["a"]
+ x0 = updated_rcs[updated_rcs_key]["x0"]
+ else:
+ K = st.session_state["rcs"][col]["K"]
+ b = st.session_state["rcs"][col]["b"]
+ a = st.session_state["rcs"][col]["a"]
+ x0 = st.session_state["rcs"][col]["x0"]
+
+ x_plot = np.linspace(0, 5 * x_actual.sum(), 200)
+
+ # Append current_channel_spends to the end of x_plot
+ x_plot = np.append(x_plot, current_channel_spends)
+
+ x, y, marginal_roi = [], [], []
+ for x_p in x_plot:
+ x.append(x_p * x_actual / x_actual.sum())
+
+ for index in range(len(x_plot)):
+ y.append(s_curve(x[index] / 10**power, K, b, a, x0))
+
+ for index in range(len(x_plot)):
+ marginal_roi.append(
+ a * y[index] * (1 - y[index] / np.maximum(K, np.finfo(float).eps))
+ )
+
+ x = (
+ np.sum(x, axis=1)
+ * st.session_state["scenario"].channels[col].conversion_rate
+ )
+ y = np.sum(y, axis=1)
+ marginal_roi = (
+ np.average(marginal_roi, axis=1)
+ / st.session_state["scenario"].channels[col].conversion_rate
+ )
+
+ roi = y / np.maximum(x, np.finfo(float).eps)
+
+ #st.write(roi[-1])
+
+ roi_current, marginal_roi_current = roi[-1], marginal_roi[-1]
+ x, y, roi, marginal_roi = (
+ x[:-1],
+ y[:-1],
+ roi[:-1],
+ marginal_roi[:-1],
+ ) # Drop data for current spends
+
+ start_value, end_value, left_value, right_value = find_segment_value(
+ x,
+ roi,
+ marginal_roi,
+ )
+
+ #st.write(roi_current)
+
+ rgba = calculate_rgba(
+ start_value,
+ end_value,
+ left_value,
+ right_value,
+ current_channel_spends,
+ )
+
+ with bin_placeholder:
+ st.markdown(
+ f"""
+
+
ROI: {round(roi_current,1)}
+
Marginal ROI: {round(marginal_roi_current,1)}
+
+ """,
+ unsafe_allow_html=True,
+ )
+
+ with st.expander("See Response Curves", expanded=True):
+ fig = plot_response_curves()
+ st.plotly_chart(fig, use_container_width=True)
+
+ _columns = st.columns(2)
+ # with _columns[0]:
+ st.subheader("Save Scenario")
+ scenario_name = st.text_input(
+ "Scenario name",
+ key="scenario_input",
+ placeholder="Scenario name",
+ label_visibility="collapsed",
+ )
+ st.button(
+ "Save",
+ on_click=lambda: save_scenario(scenario_name),
+ disabled=len(st.session_state["scenario_input"]) == 0,use_container_width=True
+ )
+
+ summary_df = pd.DataFrame(st.session_state["acutual_predicted"])
+ summary_df.drop_duplicates(subset="Channel_name", keep="last", inplace=True)
+
+ summary_df_sorted = summary_df.sort_values(by="Delta", ascending=False)
+ summary_df_sorted["Delta_percent"] = np.round(
+ ((summary_df_sorted["Optimized_spend"] / summary_df_sorted["Actual_spend"]) - 1)
+ * 100,
+ 2,
+ )
+
+ with open("summary_df.pkl", "wb") as f:
+ pickle.dump(summary_df_sorted, f)
+ # st.dataframe(summary_df_sorted)
+ # ___columns=st.columns(3)
+ # with ___columns[2]:
+ # fig=summary_plot(summary_df_sorted, x='Delta_percent', y='Channel_name', title='Delta', text_column='Delta_percent')
+ # st.plotly_chart(fig,use_container_width=True)
+ # with ___columns[0]:
+ # fig=summary_plot(summary_df_sorted, x='Actual_spend', y='Channel_name', title='Actual Spend', text_column='Actual_spend')
+ # st.plotly_chart(fig,use_container_width=True)
+ # with ___columns[1]:
+ # fig=summary_plot(summary_df_sorted, x='Optimized_spend', y='Channel_name', title='Planned Spend', text_column='Optimized_spend')
+ # st.plotly_chart(fig,use_container_width=True)
+
+
+elif auth_status == False:
+ st.error("Username/Password is incorrect")
+
+if auth_status != True:
+ try:
+ username_forgot_pw, email_forgot_password, random_password = (
+ authenticator.forgot_password("Forgot password")
+ )
+ if username_forgot_pw:
+ st.session_state["config"]["credentials"]["usernames"][username_forgot_pw][
+ "password"
+ ] = stauth.Hasher([random_password]).generate()[0]
+ send_email(email_forgot_password, random_password)
+ st.success("New password sent securely")
+ # Random password to be transferred to user securely
+ elif username_forgot_pw == False:
+ st.error("Username not found")
+ except Exception as e:
+ st.error(e)