Download app.py from Vanshcc/INSIGHT: direct link, hf CLI and curl.
- Browser
- Download file 84 kB
-
https://huggingface.co/spaces/Vanshcc/INSIGHT/resolve/main/app.py
- Command line
-
hf download hf://spaces/Vanshcc/INSIGHT/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Vanshcc/INSIGHT/resolve/main/app.py
84 kB
| import streamlit as st | |
| import pandas as pd | |
| import numpy as np | |
| import plotly.express as px | |
| import plotly.graph_objects as go | |
| from plotly.subplots import make_subplots | |
| from scipy import stats | |
| import holidays | |
| from sqlalchemy import create_engine, exc | |
| import os | |
| import tempfile | |
| # ===================== | |
| # PAGE CONFIG | |
| # ===================== | |
| st.set_page_config( | |
| page_title="Sales Insight Dashboard", | |
| page_icon="📊", | |
| layout="wide", | |
| initial_sidebar_state="expanded" | |
| ) | |
| # ===================== | |
| # CUSTOM CSS | |
| # ===================== | |
| st.markdown(""" | |
| <style> | |
| /* Main background */ | |
| .stApp { | |
| background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); | |
| } | |
| /* Metric cards */ | |
| div[data-testid="metric-container"] { | |
| background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%); | |
| border: 1px solid rgba(255,255,255,0.1); | |
| border-radius: 15px; | |
| padding: 20px; | |
| backdrop-filter: blur(10px); | |
| box-shadow: 0 8px 32px rgba(0,0,0,0.3); | |
| } | |
| div[data-testid="metric-container"] label { | |
| color: #a0aec0 !important; | |
| font-size: 0.9rem !important; | |
| } | |
| div[data-testid="metric-container"] div[data-testid="stMetricValue"] { | |
| color: #ffffff !important; | |
| font-size: 1.8rem !important; | |
| font-weight: 700 !important; | |
| } | |
| div[data-testid="metric-container"] div[data-testid="stMetricDelta"] { | |
| font-size: 0.9rem !important; | |
| } | |
| /* Headers */ | |
| h1, h2, h3 { | |
| color: #ffffff !important; | |
| font-weight: 700 !important; | |
| } | |
| /* Sidebar */ | |
| section[data-testid="stSidebar"] { | |
| background: linear-gradient(180deg, #1a1a2e 0%, #16213e 100%); | |
| border-right: 1px solid rgba(255,255,255,0.1); | |
| } | |
| section[data-testid="stSidebar"] .stMarkdown { | |
| color: #e2e8f0; | |
| } | |
| /* Cards/Containers */ | |
| .stExpander { | |
| background: rgba(255,255,255,0.05); | |
| border: 1px solid rgba(255,255,255,0.1); | |
| border-radius: 10px; | |
| } | |
| /* Dataframes */ | |
| .stDataFrame { | |
| border-radius: 10px; | |
| overflow: hidden; | |
| } | |
| /* Tabs */ | |
| .stTabs [data-baseweb="tab-list"] { | |
| gap: 8px; | |
| background: rgba(255,255,255,0.05); | |
| border-radius: 10px; | |
| padding: 5px; | |
| } | |
| .stTabs [data-baseweb="tab"] { | |
| background: transparent; | |
| border-radius: 8px; | |
| color: #a0aec0; | |
| padding: 10px 20px; | |
| } | |
| .stTabs [aria-selected="true"] { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white !important; | |
| } | |
| /* Info boxes */ | |
| .info-box { | |
| background: linear-gradient(135deg, rgba(102, 126, 234, 0.2) 0%, rgba(118, 75, 162, 0.2) 100%); | |
| border: 1px solid rgba(102, 126, 234, 0.3); | |
| border-radius: 15px; | |
| padding: 20px; | |
| margin: 10px 0; | |
| } | |
| .success-box { | |
| background: linear-gradient(135deg, rgba(72, 187, 120, 0.2) 0%, rgba(56, 161, 105, 0.2) 100%); | |
| border: 1px solid rgba(72, 187, 120, 0.3); | |
| border-radius: 15px; | |
| padding: 20px; | |
| margin: 10px 0; | |
| } | |
| .warning-box { | |
| background: linear-gradient(135deg, rgba(237, 137, 54, 0.2) 0%, rgba(221, 107, 32, 0.2) 100%); | |
| border: 1px solid rgba(237, 137, 54, 0.3); | |
| border-radius: 15px; | |
| padding: 20px; | |
| margin: 10px 0; | |
| } | |
| /* Section headers */ | |
| .section-header { | |
| background: linear-gradient(90deg, rgba(102, 126, 234, 0.3) 0%, transparent 100%); | |
| border-left: 4px solid #667eea; | |
| padding: 10px 20px; | |
| margin: 20px 0; | |
| border-radius: 0 10px 10px 0; | |
| } | |
| /* File uploader */ | |
| .stFileUploader { | |
| background: rgba(255,255,255,0.05); | |
| border: 2px dashed rgba(255,255,255,0.2); | |
| border-radius: 15px; | |
| padding: 20px; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # ===================== | |
| # AUTO COLUMN FINDER | |
| # ===================== | |
| def find_column(df, keys): | |
| cols = {c.lower(): c for c in df.columns} | |
| for k in keys: | |
| for c in cols: | |
| if k in c: | |
| return cols[c] | |
| return None | |
| def auto_insight_engine(df, date_col=None, sales_col=None, inventory_col=None): | |
| insights = [] | |
| # ---------- SALES TREND ---------- | |
| if date_col and sales_col: | |
| monthly = df.groupby(df[date_col].dt.to_period("M"))[sales_col].sum() | |
| if len(monthly) > 1: | |
| mom = monthly.pct_change() * 100 | |
| last = mom.iloc[-1] | |
| if last > 10: | |
| insights.append(("success", "📈 Strong Growth", | |
| f"Sales increased {last:.1f}% last month.")) | |
| elif last < -10: | |
| insights.append(("warning", "📉 Sharp Decline", | |
| f"Sales dropped {abs(last):.1f}% last month.")) | |
| if len(monthly) > 2: | |
| vol = monthly.pct_change().std() * 100 | |
| if vol > 25: | |
| insights.append(("warning", "⚠️ High Volatility", | |
| f"Monthly sales volatility is {vol:.1f}%.")) | |
| # ---------- PROFIT ---------- | |
| if "Profit_Margin" in df: | |
| avg = df["Profit_Margin"].mean() | |
| if avg < 15: | |
| insights.append(("warning", "🔴 Low Margins", | |
| f"Average margin only {avg:.1f}%.")) | |
| elif avg > 40: | |
| insights.append(("success", "🟢 Strong Margins", | |
| f"Healthy margin at {avg:.1f}%.")) | |
| # ---------- MARKETING ---------- | |
| if "Ad_ROI" in df: | |
| roi = df["Ad_ROI"].mean() | |
| if roi < 1: | |
| insights.append(("warning", "📢 Ads Losing Money", | |
| "ROI below 1 — campaigns are unprofitable.")) | |
| elif roi > 2: | |
| insights.append(("success", "🚀 Efficient Advertising", | |
| f"Average ROI is {roi:.2f}.")) | |
| # ---------- INVENTORY ---------- | |
| if inventory_col and "Days_of_Inventory" in df: | |
| over = (df["Days_of_Inventory"] > 60).sum() | |
| if over: | |
| insights.append(("warning", "📦 Overstock Risk", | |
| f"{over} SKUs have more than 60 days stock.")) | |
| # ---------- ANOMALIES ---------- | |
| if date_col and sales_col: | |
| daily = df.groupby(df[date_col].dt.date)[sales_col].sum() | |
| if len(daily) > 10: | |
| # Handle constant values to avoid division by zero in zscore | |
| if daily.std() > 0: | |
| z = np.abs(stats.zscore(daily)) | |
| spikes = (z > 3).sum() | |
| if spikes: | |
| insights.append(("warning", "🚨 Sales Anomalies", | |
| f"{spikes} abnormal sales days detected.")) | |
| # ---------- WEEKEND vs WEEKDAY ---------- | |
| if date_col and sales_col: | |
| df_temp = df.copy() | |
| df_temp['is_weekend'] = df_temp[date_col].dt.dayofweek >= 5 | |
| weekend_sales = df_temp[df_temp['is_weekend']][sales_col].mean() | |
| weekday_sales = df_temp[~df_temp['is_weekend']][sales_col].mean() | |
| if weekend_sales > weekday_sales * 1.2: | |
| insights.append(("success", "🎉 Weekend Surge", | |
| f"Weekend sales are {weekend_sales/weekday_sales:.1f}x higher than weekdays.")) | |
| # ---------- HOLIDAY ANALYSIS ---------- | |
| if date_col and sales_col: | |
| us_holidays = holidays.US() | |
| df_temp = df.copy() | |
| df_temp['is_holiday'] = df_temp[date_col].dt.date.apply(lambda x: x in us_holidays) | |
| holiday_sales = df_temp[df_temp['is_holiday']][sales_col].mean() | |
| normal_sales = df_temp[~df_temp['is_holiday']][sales_col].mean() | |
| if pd.notnull(holiday_sales) and pd.notnull(normal_sales) and normal_sales > 0: | |
| if holiday_sales > normal_sales * 1.3: | |
| insights.append(("success", "🎆 Holiday Boom", | |
| f"Sales on holidays are {holiday_sales/normal_sales:.1f}x higher than average!")) | |
| elif holiday_sales < normal_sales * 0.7: | |
| insights.append(("info", "💤 Holiday Slump", | |
| f"Sales tend to dip by {100 - (holiday_sales/normal_sales*100):.0f}% on holidays.")) | |
| return insights | |
| def apply_chart_style(fig, num_items=None, is_horizontal=False): | |
| """Apply consistent dark theme styling to charts""" | |
| layout_args = { | |
| 'template': 'plotly_dark', | |
| 'paper_bgcolor': 'rgba(0,0,0,0)', | |
| 'plot_bgcolor': 'rgba(0,0,0,0)', | |
| 'showlegend': False, | |
| 'margin': dict(l=120, t=50, r=20, b=50) | |
| } | |
| if is_horizontal and num_items: | |
| layout_args['height'] = max(400, num_items * 35) | |
| fig.update_layout(**layout_args) | |
| return fig | |
| # ===================== | |
| # SIDEBAR | |
| # ===================== | |
| with st.sidebar: | |
| st.markdown("# 📊 Sales Insight") | |
| st.markdown("---") | |
| source_type = st.radio("Data Source", ["📂 CSV File", "🗄️ Database"], horizontal=True) | |
| df = None | |
| if source_type == "📂 CSV File": | |
| uploaded_files = st.file_uploader( | |
| "Upload CSV File(s)", | |
| type=['csv'], | |
| accept_multiple_files=True, | |
| help="Upload one or more sales data CSV files" | |
| ) | |
| if not uploaded_files: | |
| st.info("Using default dataset") | |
| try: | |
| df = pd.read_csv("6_months_sells.csv") | |
| except: | |
| st.error("No default file found. Please upload a CSV.") | |
| st.stop() | |
| else: | |
| try: | |
| dfs = [] | |
| for f in uploaded_files: | |
| dfs.append(pd.read_csv(f)) | |
| df = pd.concat(dfs, ignore_index=True) | |
| if len(uploaded_files) > 1: | |
| st.success(f"✅ Successfully merged {len(uploaded_files)} files into one dataset ({len(df):,} rows)") | |
| except Exception as e: | |
| st.error(f"Error merging files: {e}") | |
| st.stop() | |
| else: | |
| st.markdown("### 🔌 Database Connection") | |
| with st.expander("📝 Connection URI Guide"): | |
| st.markdown(""" | |
| **Google Cloud SQL / AWS RDS / Azure SQL** | |
| Support for standard PostgreSQL, MySQL, and other drivers. | |
| **Templates:** | |
| - **PostgreSQL**: `postgresql+psycopg2://USER:PASSWORD@IP:5432/DB_NAME` | |
| - **MySQL**: `mysql+pymysql://USER:PASSWORD@IP:3306/DB_NAME` | |
| - **SQL Server**: `mssql+pyodbc://USER:PASSWORD@IP/DB_NAME` | |
| - **SQLite**: `sqlite:///data.db` | |
| *Note: Ensure your database allows public IP access or use a proxy.* | |
| """) | |
| with st.expander("Connection Details", expanded=True): | |
| conn_method = st.tabs(["🧱 Form Builder", "🔗 Raw URI String"]) | |
| db_uri = "" | |
| with conn_method[0]: | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| db_type = st.selectbox("Database Type", ["PostgreSQL", "MySQL", "SQL Server", "SQLite"]) | |
| with col2: | |
| if db_type == "SQLite": | |
| db_host = "" | |
| db_port = "" | |
| elif db_type == "PostgreSQL": | |
| db_port = st.text_input("Port", "5432") | |
| elif db_type == "MySQL": | |
| db_port = st.text_input("Port", "3306") | |
| elif db_type == "SQL Server": | |
| db_port = st.text_input("Port", "1433") | |
| if db_type != "SQLite": | |
| db_host = st.text_input("Host IP/Address", placeholder="34.12.34.56 or db.example.com") | |
| db_name = st.text_input("Database Name", placeholder="sales_db") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| db_user = st.text_input("Username", placeholder="admin") | |
| with col2: | |
| db_pass = st.text_input("Password", type="password") | |
| # Construct URI | |
| if st.checkbox("Show Generated URI"): | |
| # Preview without password | |
| preview_uri = f"{db_type.lower()}://{db_user}:****@{db_host}:{db_port}/{db_name}" | |
| st.caption(f"URI Preview: `{preview_uri}`") | |
| if db_type == "PostgreSQL": | |
| db_uri = f"postgresql+psycopg2://{db_user}:{db_pass}@{db_host}:{db_port}/{db_name}" | |
| elif db_type == "MySQL": | |
| db_uri = f"mysql+pymysql://{db_user}:{db_pass}@{db_host}:{db_port}/{db_name}" | |
| elif db_type == "SQL Server": | |
| db_uri = f"mssql+pyodbc://{db_user}:{db_pass}@{db_host}:{db_port}/{db_name}?driver=ODBC+Driver+17+for+SQL+Server" | |
| else: | |
| # SQLite | |
| sqlite_path = st.text_input("Database Path", "data.db") | |
| db_uri = f"sqlite:///{sqlite_path}" | |
| with conn_method[1]: | |
| db_uri_input = st.text_input( | |
| "Connection String", | |
| placeholder="postgresql+psycopg2://user:pass@service-account.aivencloud.com:5432/db", | |
| help="Enter your SQLAlchemy connection string directly" | |
| ) | |
| if db_uri_input: | |
| db_uri = db_uri_input.strip() | |
| # Auto-correction | |
| if db_uri.startswith("postgres://"): | |
| db_uri = db_uri.replace("postgres://", "postgresql+psycopg2://") | |
| query = st.text_area( | |
| "SQL Query", | |
| value="SELECT * FROM sales_data LIMIT 5000", | |
| help="Enter your SQL query to fetch data" | |
| ) | |
| # SSL Configuration | |
| st.markdown("#### 🔐 Security (Optional)") | |
| use_ssl = st.checkbox("Enable SSL/TLS (Required for Aiven, Azure, etc.)") | |
| ssl_ca = None | |
| if use_ssl: | |
| ssl_ca = st.file_uploader("Upload CA Certificate (.pem)", type=["pem", "crt", "ca"]) | |
| if st.button("🔌 Connect & Load"): | |
| if not db_uri or not query: | |
| st.warning("Please provide Connection Details and Query.") | |
| else: | |
| tmp_ca_path = None | |
| try: | |
| connect_args = {} | |
| # Handle SSL Cert creation | |
| if use_ssl and ssl_ca: | |
| with tempfile.NamedTemporaryFile(delete=False, suffix='.pem') as tmp: | |
| tmp.write(ssl_ca.getvalue()) | |
| tmp_ca_path = tmp.name | |
| # Configure based on dialect | |
| if "postgres" in db_uri: | |
| connect_args = { | |
| "sslmode": "require", | |
| "sslrootcert": tmp_ca_path | |
| } | |
| elif "mysql" in db_uri: | |
| connect_args = { | |
| "ssl": {"ca": tmp_ca_path} | |
| } | |
| # Add a spinner for better UX on remote connections | |
| with st.spinner("Connecting to database..."): | |
| engine = create_engine(db_uri, connect_args=connect_args) | |
| with engine.connect() as conn: | |
| df = pd.read_sql(query, conn) | |
| except exc.NoSuchModuleError as e: | |
| st.error("🚫 Missing Driver Error") | |
| st.warning(f"The required database driver is missing: {e}") | |
| st.info("💡 Tip: Standard Google Cloud connections require specific libraries. \n\n" | |
| "- For Postgres: `pip install psycopg2-binary`\n" | |
| "- For MySQL: `pip install pymysql`\n" | |
| "- For SQL Server: `pip install pyodbc`") | |
| except Exception as e: | |
| st.error(f"Connection Error: {e}") | |
| st.markdown("Try checking: 1. IP Whitelisting 2. SSL Configuration 3. Correct Credentials") | |
| finally: | |
| # Cleanup temp file | |
| if tmp_ca_path and os.path.exists(tmp_ca_path): | |
| os.unlink(tmp_ca_path) | |
| if df is None: | |
| st.info("👈 Connect to a database to proceed") | |
| st.stop() | |
| # Data cleaning - User Configurable | |
| nan_count = df.isnull().sum().sum() | |
| with st.expander("🧹 Data Cleaning & Handling", expanded=(nan_count > 0)): | |
| if nan_count > 0: | |
| st.warning(f"⚠️ Found {nan_count:,} missing values in your data.") | |
| # Show breakdown | |
| null_cols = df.columns[df.isnull().any()].tolist() | |
| st.write("Columns with missing data:", null_cols) | |
| clean_mode = st.radio( | |
| "Choose how to handle missing values:", | |
| ["✨ Auto-Fill (Recommended)", "🗑️ Drop Rows", "🛑 Keep Raw Data"], | |
| help="Auto-Fill replaces numbers with 0 and text with 'Unknown'. Drop Rows removes the entire record." | |
| ) | |
| if clean_mode == "✨ Auto-Fill (Recommended)": | |
| for col in df.columns: | |
| if df[col].dtype in ['float64', 'int64']: | |
| df[col] = df[col].fillna(0) | |
| else: | |
| df[col] = df[col].fillna('Unknown') | |
| st.success("✅ Applied: Numeric -> 0, Text -> 'Unknown'") | |
| elif clean_mode == "🗑️ Drop Rows": | |
| initial_rows = len(df) | |
| df = df.dropna() | |
| st.success(f"✅ Dropped {initial_rows - len(df)} rows") | |
| elif clean_mode == "🛑 Keep Raw Data": | |
| st.info("⚠️ Keeping data as-is. Some charts may show errors.") | |
| else: | |
| st.success("✅ Data is clean! No missing values found.") | |
| st.success(f"✅ Loaded {len(df):,} records") | |
| st.markdown("---") | |
| # ===================== | |
| # COLUMN MAPPING | |
| # ===================== | |
| st.markdown("### ⚙️ Column Mapping") | |
| # helper to get index for selectbox | |
| def get_index(options, target): | |
| try: | |
| return options.index(target) | |
| except ValueError: | |
| return 0 | |
| all_cols = ["None"] + list(df.columns) | |
| # Auto-detect defaults | |
| default_date = find_column(df, ["date", "month", "time", "day", "period"]) | |
| default_sales = find_column(df, ["sales", "revenue", "amount", "value", "turnover"]) | |
| default_inventory = find_column(df, ["inventory", "stock", "quantity", "volume"]) | |
| default_ad = find_column(df, ["adcost", "ad_cost", "marketing", "spend", "ads"]) | |
| default_clicks = find_column(df, ["click", "visits"]) | |
| default_impr = find_column(df, ["impr", "views", "reach"]) | |
| default_brand = find_column(df, ["brand", "manufacturer", "maker", "vendor"]) | |
| default_cat = find_column(df, ["subcategory", "category", "type", "segment"]) | |
| default_price = find_column(df, ["price", "mrp", "selling_price"]) | |
| default_cost = find_column(df, ["mfrcost", "cost", "mfr_cost", "buying_price"]) | |
| default_cpc = find_column(df, ["avgcpc", "cpc", "avg_cpc", "cost_per_click"]) | |
| default_pos = find_column(df, ["position", "our position", "rank", "ranking"]) | |
| default_cart = find_column(df, ["abdcarts", "abandoned", "cart", "basket"]) | |
| with st.expander("⚙️ Column Configuration", expanded=True): | |
| st.caption("Map your CSV columns to the dashboard fields.") | |
| st.markdown("#### 📌 Core Columns") | |
| date_col = st.selectbox("Date Column", all_cols, index=get_index(all_cols, default_date)) | |
| if date_col == "None": date_col = None | |
| sales_col = st.selectbox("Sales/Revenue Column", all_cols, index=get_index(all_cols, default_sales)) | |
| if sales_col == "None": sales_col = None | |
| brand_col = st.selectbox("Brand Column", all_cols, index=get_index(all_cols, default_brand)) | |
| if brand_col == "None": brand_col = None | |
| cat_col = st.selectbox("Category Column", all_cols, index=get_index(all_cols, default_cat)) | |
| if cat_col == "None": cat_col = None | |
| st.markdown("#### 📦 Product & Inventory") | |
| inventory_col = st.selectbox("Inventory/Stock Column", all_cols, index=get_index(all_cols, default_inventory)) | |
| if inventory_col == "None": inventory_col = None | |
| price_col = st.selectbox("Unit Price Column", all_cols, index=get_index(all_cols, default_price)) | |
| if price_col == "None": price_col = None | |
| cost_col = st.selectbox("Unit Cost Column", all_cols, index=get_index(all_cols, default_cost)) | |
| if cost_col == "None": cost_col = None | |
| st.markdown("#### 📢 Marketing & Metrics") | |
| ad_col = st.selectbox("Ad Cost Column", all_cols, index=get_index(all_cols, default_ad)) | |
| if ad_col == "None": ad_col = None | |
| clicks_col = st.selectbox("Clicks Column", all_cols, index=get_index(all_cols, default_clicks)) | |
| if clicks_col == "None": clicks_col = None | |
| impr_col = st.selectbox("Impressions Column", all_cols, index=get_index(all_cols, default_impr)) | |
| if impr_col == "None": impr_col = None | |
| avgcpc_col = st.selectbox("CPC Column", all_cols, index=get_index(all_cols, default_cpc)) | |
| if avgcpc_col == "None": avgcpc_col = None | |
| position_col = st.selectbox("Search Position Column", all_cols, index=get_index(all_cols, default_pos)) | |
| if position_col == "None": position_col = None | |
| cart_col = st.selectbox("Abandoned Carts Column", all_cols, index=get_index(all_cols, default_cart)) | |
| if cart_col == "None": cart_col = None | |
| # Validation | |
| if not date_col or not sales_col: | |
| st.warning("⚠️ 'Date' and 'Sales' columns are recommended for full functionality.") | |
| detected = { | |
| "📅 Date": date_col, | |
| "💰 Sales": sales_col, | |
| "📦 Inventory": inventory_col, | |
| "📢 Ad Cost": ad_col, | |
| "🖱️ Clicks": clicks_col, | |
| "👁️ Impressions": impr_col, | |
| "🏷️ Brand": brand_col, | |
| "📂 Category": cat_col, | |
| "💵 Price": price_col, | |
| "🏭 Mfr Cost": cost_col, | |
| "📊 Position": position_col, | |
| "🛒 Abnd Carts": cart_col, | |
| } | |
| st.markdown("### 📋 Active Columns") | |
| active_cols = [] | |
| for name, col in detected.items(): | |
| if col: | |
| active_cols.append(f"<small>{name}: `{col}`</small>") | |
| if active_cols: | |
| st.markdown("<br>".join(active_cols), unsafe_allow_html=True) | |
| else: | |
| st.info("No columns mapped yet.") | |
| # ===================== | |
| # FILTERS | |
| # ===================== | |
| st.markdown("---") | |
| st.markdown("### 🔍 Filters") | |
| # Convert date column first (before storing original) | |
| if date_col: | |
| df[date_col] = pd.to_datetime(df[date_col], dayfirst=True) | |
| # Store original df (with datetime converted) | |
| df_original = df.copy() | |
| # Brand filter | |
| if brand_col: | |
| brands = ["All"] + sorted(df[brand_col].dropna().unique().tolist()) | |
| selected_brand = st.selectbox("Select Brand", brands) | |
| if selected_brand != "All": | |
| df = df[df[brand_col] == selected_brand] | |
| # Category filter | |
| if cat_col: | |
| categories = ["All"] + sorted(df[cat_col].dropna().unique().tolist()) | |
| selected_category = st.selectbox("Select Category", categories) | |
| if selected_category != "All": | |
| df = df[df[cat_col] == selected_category] | |
| # Date range filter | |
| if date_col: | |
| df[date_col] = pd.to_datetime(df[date_col]) | |
| min_date = df[date_col].min().date() | |
| max_date = df[date_col].max().date() | |
| date_range = st.date_input( | |
| "Date Range", | |
| value=(min_date, max_date), | |
| min_value=min_date, | |
| max_value=max_date | |
| ) | |
| if len(date_range) == 2: | |
| df = df[(df[date_col].dt.date >= date_range[0]) & (df[date_col].dt.date <= date_range[1])] | |
| # Show filtered count | |
| if len(df) != len(df_original): | |
| st.info(f"📊 Showing {len(df):,} of {len(df_original):,} records") | |
| st.markdown("---") | |
| # Quick stats | |
| st.markdown("### 📈 Quick Stats") | |
| if sales_col: | |
| st.metric("Filtered Sales", f"{df[sales_col].sum():,.0f}") | |
| if brand_col: | |
| st.metric("Brands", f"{df[brand_col].nunique()}") | |
| if cat_col: | |
| st.metric("Categories", f"{df[cat_col].nunique()}") | |
| # ===================== | |
| # PROCESS DATA | |
| # ===================== | |
| # Date conversion already done in filters section | |
| # Calculate derived metrics | |
| if price_col and cost_col: | |
| df["Profit_Margin"] = (df[price_col] - df[cost_col]) / df[price_col].replace(0, np.nan) * 100 | |
| if clicks_col and impr_col: | |
| df["CTR"] = df[clicks_col] / df[impr_col].replace(0, np.nan) * 100 | |
| if sales_col and clicks_col: | |
| df["Conversion_Rate"] = df[sales_col] / df[clicks_col].replace(0, np.nan) * 100 | |
| if inventory_col and sales_col: | |
| df["Inventory_Turnover"] = df[sales_col] / df[inventory_col].replace(0, np.nan) | |
| if ad_col and sales_col: | |
| df["Cost_per_Sale"] = df[ad_col] / df[sales_col].replace(0, np.nan) | |
| df["Ad_ROI"] = df[sales_col] / df[ad_col].replace(0, np.nan) | |
| if inventory_col and sales_col: | |
| df["IS_Ratio"] = df[inventory_col] / df[sales_col].replace(0, np.nan) | |
| # ===================== | |
| # HEADER | |
| # ===================== | |
| st.markdown(""" | |
| <div style="text-align: center; padding: 20px 0;"> | |
| <h1 style="font-size: 2.5rem; margin-bottom: 10px;"> | |
| 📊 <span style="background: linear-gradient(90deg, #667eea, #764ba2, #f093fb); | |
| -webkit-background-clip: text; -webkit-text-fill-color: transparent;">Sales Insight Dashboard</span> | |
| </h1> | |
| <p style="color: #a0aec0; font-size: 1.1rem;">Comprehensive Business Intelligence & Analytics</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # ===================== | |
| # KPI METRICS ROW | |
| # ===================== | |
| st.markdown('<div class="section-header"><h3>🎯 Executive KPIs</h3></div>', unsafe_allow_html=True) | |
| col1, col2, col3, col4, col5 = st.columns(5) | |
| with col1: | |
| if sales_col: | |
| st.metric("Total Sales", f"{df[sales_col].sum():,.0f}", "Revenue") | |
| with col2: | |
| if inventory_col: | |
| st.metric("Total Inventory", f"{df[inventory_col].sum():,.0f}", "Units") | |
| with col3: | |
| if ad_col: | |
| st.metric("Ad Spend", f"${df[ad_col].sum():,.0f}", "Marketing") | |
| with col4: | |
| if clicks_col: | |
| st.metric("Total Clicks", f"{df[clicks_col].sum():,.0f}", "Engagement") | |
| with col5: | |
| if impr_col: | |
| st.metric("Impressions", f"{df[impr_col].sum()/1e6:.1f}M", "Reach") | |
| # Second row of KPIs | |
| col1, col2, col3, col4, col5 = st.columns(5) | |
| with col1: | |
| if "Profit_Margin" in df.columns: | |
| st.metric("Avg Margin", f"{df['Profit_Margin'].mean():.1f}%", "Profitability") | |
| with col2: | |
| if "CTR" in df.columns: | |
| st.metric("Avg CTR", f"{df['CTR'].mean():.2f}%", "Click Rate") | |
| with col3: | |
| if "Conversion_Rate" in df.columns: | |
| st.metric("Conversion", f"{df['Conversion_Rate'].mean():.4f}%", "Sales/Clicks") | |
| with col4: | |
| if "Ad_ROI" in df.columns: | |
| st.metric("Ad ROI", f"{df['Ad_ROI'].mean():.2f}", "Sales/$") | |
| with col5: | |
| st.metric("Records", f"{len(df):,}", "Data Points") | |
| # ===================== | |
| # TABS FOR DIFFERENT ANALYSES | |
| # ===================== | |
| st.markdown("<br>", unsafe_allow_html=True) | |
| tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8, tab9, tab10, tab11, tab12 = st.tabs([ | |
| "📈 Trends", "🏆 Performance", "💰 Profitability", | |
| "📢 Marketing", "📦 Inventory", "🔮 Forecast", "📊 Advanced", | |
| "🔄 Comparison", "🔍 Data Explorer", "🎯 Goals", "🛒 Conversion", | |
| "🤖 Insights" | |
| ]) | |
| # ===================== | |
| # TAB 1: TRENDS | |
| # ===================== | |
| with tab1: | |
| st.markdown('<div class="section-header"><h3>📈 Sales Trends & Patterns</h3></div>', unsafe_allow_html=True) | |
| if date_col and sales_col: | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| # Monthly trend | |
| monthly = df.groupby(df[date_col].dt.to_period("M"))[sales_col].sum() | |
| monthly.index = monthly.index.astype(str) | |
| fig = px.bar( | |
| x=monthly.index, y=monthly.values, | |
| title="Monthly Sales Trend", | |
| labels={'x': 'Month', 'y': 'Sales'}, | |
| color=monthly.values, | |
| color_continuous_scale='Viridis' | |
| ) | |
| fig.update_layout( | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)', | |
| showlegend=False | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| with col2: | |
| # Daily sales with moving average | |
| daily = df.groupby(df[date_col].dt.date)[sales_col].sum().reset_index() | |
| daily.columns = ['Date', 'Sales'] | |
| # --- Auto-Pattern Detection --- | |
| # Calculate autocorrelation to find best window | |
| sales_series = pd.Series(daily['Sales'].values) | |
| lag_7_corr = sales_series.autocorr(lag=7) | |
| lag_30_corr = sales_series.autocorr(lag=30) | |
| optimal_window = 7 # default | |
| pattern_msg = "Defaulting to weekly trend." | |
| if lag_30_corr > lag_7_corr and lag_30_corr > 0.3: | |
| optimal_window = 30 | |
| pattern_msg = f"✨ Detected strong monthly seasonality (Corr: {lag_30_corr:.2f}). Applied 30-Day MA." | |
| elif lag_7_corr > 0.3: | |
| optimal_window = 7 | |
| pattern_msg = f"✨ Detected strong weekly seasonality (Corr: {lag_7_corr:.2f}). Applied 7-Day MA." | |
| else: | |
| optimal_window = 7 | |
| pattern_msg = "No strong pattern detected. Using 7-Day MA for smoothing." | |
| daily['MA_Optimal'] = daily['Sales'].rolling(optimal_window, min_periods=1).mean() | |
| daily['MA_7'] = daily['Sales'].rolling(7, min_periods=1).mean() | |
| daily['MA_30'] = daily['Sales'].rolling(30, min_periods=1).mean() | |
| # View Toggle | |
| view_mode = st.radio("Daily Chart View", | |
| ["✨ Smart MA (Auto)", "Detailed (All)", "Manual Comparison (7 vs 30)"], | |
| horizontal=True, label_visibility="collapsed") | |
| if view_mode == "✨ Smart MA (Auto)": | |
| st.caption(pattern_msg) | |
| fig = go.Figure() | |
| if view_mode == "Detailed (All)": | |
| fig.add_trace(go.Scatter(x=daily['Date'], y=daily['Sales'], | |
| name='Daily Sales', mode='lines', | |
| line=dict(color='#667eea', width=1), opacity=0.4)) | |
| fig.add_trace(go.Scatter(x=daily['Date'], y=daily['MA_7'], | |
| name='7-Day MA', mode='lines', | |
| line=dict(color='#f093fb', width=2))) | |
| elif view_mode == "Manual Comparison (7 vs 30)": | |
| fig.add_trace(go.Scatter(x=daily['Date'], y=daily['MA_7'], | |
| name='7-Day MA', mode='lines', | |
| line=dict(color='#f093fb', width=2))) | |
| fig.add_trace(go.Scatter(x=daily['Date'], y=daily['MA_30'], | |
| name='30-Day MA', mode='lines', | |
| line=dict(color='#48bb78', width=2))) | |
| else: # Smart MA | |
| fig.add_trace(go.Scatter(x=daily['Date'], y=daily['Sales'], | |
| name='Daily Sales', mode='lines', | |
| line=dict(color='#667eea', width=1), opacity=0.2)) | |
| fig.add_trace(go.Scatter(x=daily['Date'], y=daily['MA_Optimal'], | |
| name=f'{optimal_window}-Day Optimal MA', mode='lines', | |
| line=dict(color='#f093fb', width=3))) | |
| fig.update_layout( | |
| title='Daily Sales Trends', | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)', | |
| legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1) | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # Holiday Analysis | |
| st.markdown("#### 🎆 Holiday Effects") | |
| us_holidays = holidays.US() | |
| df['Is_Holiday'] = df[date_col].dt.date.apply(lambda x: x in us_holidays) | |
| df['Holiday_Name'] = df[date_col].dt.date.apply(lambda x: us_holidays.get(x)) | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| holiday_stats = df.groupby('Is_Holiday')[sales_col].mean() | |
| if len(holiday_stats) > 1: | |
| fig = px.bar( | |
| x=['Normal Days', 'Holidays'], | |
| y=[holiday_stats.get(False, 0), holiday_stats.get(True, 0)], | |
| title="Average Sales: Normal vs Holidays", | |
| color=['Normal', 'Holiday'], | |
| color_discrete_sequence=['#667eea', '#f093fb'] | |
| ) | |
| apply_chart_style(fig) | |
| st.plotly_chart(fig, use_container_width=True) | |
| else: | |
| st.info("Not enough holiday data for comparison.") | |
| with col2: | |
| holiday_breakdown = df[df['Is_Holiday'] == True].groupby('Holiday_Name')[sales_col].sum().sort_values(ascending=True) | |
| if not holiday_breakdown.empty: | |
| fig = px.bar( | |
| x=holiday_breakdown.values, y=holiday_breakdown.index, | |
| orientation='h', | |
| title="Total Sales by Holiday", | |
| color=holiday_breakdown.values, | |
| color_continuous_scale='RdYlGn' | |
| ) | |
| apply_chart_style(fig, len(holiday_breakdown), is_horizontal=True) | |
| st.plotly_chart(fig, use_container_width=True) | |
| else: | |
| st.info("No sales occurred on holidays in this dataset.") | |
| # Weekly pattern | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| df["DayOfWeek"] = df[date_col].dt.day_name() | |
| weekly = df.groupby("DayOfWeek")[sales_col].mean().reindex([ | |
| "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" | |
| ]) | |
| fig = px.bar( | |
| x=weekly.index, y=weekly.values, | |
| title="Average Sales by Day of Week", | |
| color=weekly.values, | |
| color_continuous_scale='RdYlGn' | |
| ) | |
| fig.update_layout( | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)', | |
| showlegend=False | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| with col2: | |
| # Seasonal Index | |
| monthly_vals = df.groupby(df[date_col].dt.to_period("M"))[sales_col].sum() | |
| seasonal_idx = (monthly_vals / monthly_vals.mean() * 100).round(1) | |
| seasonal_idx.index = seasonal_idx.index.astype(str) | |
| fig = go.Figure(go.Bar( | |
| x=seasonal_idx.index, | |
| y=seasonal_idx.values - 100, | |
| base=100, | |
| marker_color=['#48bb78' if v > 100 else '#f56565' for v in seasonal_idx.values] | |
| )) | |
| fig.add_hline(y=100, line_dash="dash", line_color="white", opacity=0.5) | |
| fig.update_layout( | |
| title='Seasonal Index (100 = Average)', | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)', | |
| yaxis_title='Index' | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # ===================== | |
| # TAB 2: PERFORMANCE | |
| # ===================== | |
| with tab2: | |
| st.markdown('<div class="section-header"><h3>🏆 Brand & Category Performance</h3></div>', unsafe_allow_html=True) | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| if brand_col and sales_col: | |
| brand_sales = df.groupby(brand_col)[sales_col].sum().sort_values(ascending=True) | |
| fig = px.bar( | |
| x=brand_sales.values, y=brand_sales.index, | |
| orientation='h', | |
| title="Sales by Brand", | |
| color=brand_sales.values, | |
| color_continuous_scale='Viridis' | |
| ) | |
| fig.update_layout( | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)', | |
| showlegend=False, | |
| yaxis_title='', | |
| xaxis_title='Sales', | |
| height=max(400, len(brand_sales) * 30), # Dynamic height | |
| margin=dict(l=100, t=50) # Left margin for labels | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| with col2: | |
| if cat_col and sales_col: | |
| cat_sales = df.groupby(cat_col)[sales_col].sum() | |
| fig = px.pie( | |
| values=cat_sales.values, names=cat_sales.index, | |
| title="Sales by Category", | |
| hole=0.4, | |
| color_discrete_sequence=px.colors.qualitative.Set3 | |
| ) | |
| fig.update_layout( | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)' | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # Brand x Category Matrix | |
| if brand_col and cat_col and sales_col: | |
| st.markdown("#### 📊 Brand × Category Sales Matrix") | |
| matrix = pd.pivot_table(df, values=sales_col, index=brand_col, columns=cat_col, aggfunc='sum') | |
| fig = px.imshow( | |
| matrix, | |
| title="Sales Heatmap: Brand vs Category", | |
| color_continuous_scale='Viridis', | |
| aspect='auto' | |
| ) | |
| fig.update_layout( | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)' | |
| ) | |
| st.plotly_chart(fig, width="stretch") | |
| # Top/Bottom performers | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.markdown("#### 🥇 Top 10 Best Sellers") | |
| top_cols = [sales_col] | |
| if brand_col: top_cols.insert(0, brand_col) | |
| if cat_col: top_cols.insert(0, cat_col) | |
| st.dataframe(df.nlargest(10, sales_col)[top_cols], use_container_width=True) | |
| with col2: | |
| st.markdown("#### 📉 Bottom 10 Performers") | |
| st.dataframe(df.nsmallest(10, sales_col)[top_cols], use_container_width=True) | |
| # ===================== | |
| # TAB 3: PROFITABILITY | |
| # ===================== | |
| with tab3: | |
| st.markdown('<div class="section-header"><h3>💰 Profitability Analysis</h3></div>', unsafe_allow_html=True) | |
| if "Profit_Margin" in df.columns: | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| st.metric("Average Margin", f"{df['Profit_Margin'].mean():.1f}%") | |
| with col2: | |
| st.metric("Min Margin", f"{df['Profit_Margin'].min():.1f}%") | |
| with col3: | |
| st.metric("Max Margin", f"{df['Profit_Margin'].max():.1f}%") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| if brand_col: | |
| brand_margin = df.groupby(brand_col)["Profit_Margin"].mean().sort_values(ascending=True) | |
| fig = px.bar( | |
| x=brand_margin.values, y=brand_margin.index, | |
| orientation='h', | |
| title="Profit Margin by Brand", | |
| color=brand_margin.values, | |
| color_continuous_scale='RdYlGn' | |
| ) | |
| apply_chart_style(fig, len(brand_margin), is_horizontal=True) | |
| st.plotly_chart(fig, use_container_width=True) | |
| with col2: | |
| # Margin distribution | |
| fig = px.histogram( | |
| df, x="Profit_Margin", | |
| title="Profit Margin Distribution", | |
| nbins=50, | |
| color_discrete_sequence=['#667eea'] | |
| ) | |
| fig.update_layout( | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)' | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # Price Tier Analysis | |
| if price_col: | |
| st.markdown("#### 💵 Price Tier Analysis") | |
| df["Price_Tier"] = pd.qcut(df[price_col], q=4, labels=["Budget", "Economy", "Premium", "Luxury"]) | |
| tier_data = df.groupby("Price_Tier", observed=True).agg({ | |
| sales_col: ['sum', 'mean', 'count'] | |
| }).round(0) | |
| tier_data.columns = ['Total Sales', 'Avg Sales', 'Products'] | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.dataframe(tier_data, use_container_width=True) | |
| with col2: | |
| tier_sales = df.groupby("Price_Tier", observed=True)[sales_col].sum() | |
| fig = px.pie( | |
| values=tier_sales.values, names=tier_sales.index, | |
| title="Revenue by Price Tier", | |
| hole=0.4, | |
| color_discrete_sequence=['#667eea', '#764ba2', '#f093fb', '#48bb78'] | |
| ) | |
| fig.update_layout( | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)' | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| else: | |
| st.warning("Price and Cost columns required for profitability analysis") | |
| # ===================== | |
| # TAB 4: MARKETING | |
| # ===================== | |
| with tab4: | |
| st.markdown('<div class="section-header"><h3>📢 Marketing Performance</h3></div>', unsafe_allow_html=True) | |
| col1, col2, col3, col4, col5 = st.columns(5) | |
| with col1: | |
| if "CTR" in df.columns: | |
| st.metric("Avg CTR", f"{df['CTR'].mean():.2f}%") | |
| with col2: | |
| if "Conversion_Rate" in df.columns: | |
| st.metric("Avg Conversion", f"{df['Conversion_Rate'].mean():.4f}%") | |
| with col3: | |
| if "Ad_ROI" in df.columns: | |
| st.metric("Avg ROI", f"{df['Ad_ROI'].mean():.2f}") | |
| with col4: | |
| if avgcpc_col: | |
| st.metric("Avg CPC", f"${df[avgcpc_col].mean():.2f}") | |
| with col5: | |
| if position_col: | |
| st.metric("Avg Position", f"{df[position_col].mean():.1f}", help="Lower is better (0 = Top Rank)") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| if brand_col and "CTR" in df.columns: | |
| brand_ctr = df.groupby(brand_col)["CTR"].mean().sort_values(ascending=True) | |
| fig = px.bar( | |
| x=brand_ctr.values, y=brand_ctr.index, | |
| orientation='h', | |
| title="CTR by Brand", | |
| color=brand_ctr.values, | |
| color_continuous_scale='Blues' | |
| ) | |
| apply_chart_style(fig, len(brand_ctr), is_horizontal=True) | |
| st.plotly_chart(fig, use_container_width=True) | |
| with col2: | |
| if brand_col and "Ad_ROI" in df.columns: | |
| brand_roi = df.groupby(brand_col)["Ad_ROI"].mean().sort_values(ascending=True) | |
| fig = px.bar( | |
| x=brand_roi.values, y=brand_roi.index, | |
| orientation='h', | |
| title="Ad ROI by Brand", | |
| color=brand_roi.values, | |
| color_continuous_scale='Greens' | |
| ) | |
| apply_chart_style(fig, len(brand_roi), is_horizontal=True) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # Cost per Sale | |
| if brand_col and "Cost_per_Sale" in df.columns: | |
| st.markdown("#### 💸 Marketing Cost per Sale") | |
| cps = df.groupby(brand_col)["Cost_per_Sale"].mean().sort_values() | |
| fig = px.bar( | |
| x=cps.index, y=cps.values, | |
| title="Cost per Sale by Brand (Lower is Better)", | |
| color=cps.values, | |
| color_continuous_scale='RdYlGn_r' | |
| ) | |
| fig.update_layout( | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)', | |
| showlegend=False | |
| ) | |
| st.plotly_chart(fig, width="stretch") | |
| # Search Position Analysis | |
| if position_col: | |
| st.markdown('<div class="section-header"><h3>🔍 Search Visibility & Position</h3></div>', unsafe_allow_html=True) | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| # Avg Position by Brand (Lower is better) | |
| if brand_col: | |
| brand_pos = df.groupby(brand_col)[position_col].mean().sort_values(ascending=True) # Ascending because lower is better | |
| fig = px.bar( | |
| x=brand_pos.values, y=brand_pos.index, | |
| orientation='h', | |
| title="Avg Search Position by Brand (Lower is Better)", | |
| color=brand_pos.values, | |
| color_continuous_scale='RdYlGn_r' # Red for high numbers (bad), Green for low (good) | |
| ) | |
| apply_chart_style(fig, len(brand_pos), is_horizontal=True) | |
| st.plotly_chart(fig, use_container_width=True) | |
| with col2: | |
| # Position Distribution | |
| fig = px.histogram( | |
| df, x=position_col, | |
| title="Search Position Distribution", | |
| nbins=20, | |
| color_discrete_sequence=['#f093fb'] | |
| ) | |
| fig.update_layout( | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)' | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # Daily Position Trend | |
| if date_col: | |
| st.markdown("#### 📉 Daily Average Position Trend") | |
| daily_pos = df.groupby(df[date_col].dt.date)[position_col].mean().reset_index() | |
| daily_pos.columns = ['Date', 'Avg Position'] | |
| fig = go.Figure() | |
| fig.add_trace(go.Scatter( | |
| x=daily_pos['Date'], y=daily_pos['Avg Position'], | |
| mode='lines', | |
| line=dict(color='#f093fb', width=3), | |
| name='Avg Position', | |
| fill='tozeroy', | |
| fillcolor='rgba(240, 147, 251, 0.1)' | |
| )) | |
| fig.update_layout( | |
| title="Average Search Position (Inverted Axis - Lower is Better)", | |
| yaxis=dict(autorange="reversed", title="Avg Position"), # Invert Y axis so 0 is at top | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)' | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # ===================== | |
| # TAB 5: INVENTORY | |
| # ===================== | |
| with tab5: | |
| st.markdown('<div class="section-header"><h3>📦 Inventory Analysis</h3></div>', unsafe_allow_html=True) | |
| if inventory_col and sales_col: | |
| # Inventory Health | |
| daily_sales_rate = df[sales_col].sum() / 90 | |
| df["Days_of_Inventory"] = df[inventory_col] / daily_sales_rate | |
| overstocked = df[df["Days_of_Inventory"] > 60] | |
| understocked = df[df["Days_of_Inventory"] < 7] | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| st.metric("Avg Days of Inventory", f"{df['Days_of_Inventory'].mean():.0f} days") | |
| with col2: | |
| st.metric("Overstocked (>60 days)", f"{len(overstocked):,}", f"{len(overstocked)/len(df)*100:.1f}%") | |
| with col3: | |
| st.metric("Understocked (<7 days)", f"{len(understocked):,}", f"{len(understocked)/len(df)*100:.1f}%") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| if brand_col: | |
| brand_turnover = df.groupby(brand_col)["Inventory_Turnover"].mean().sort_values(ascending=True) | |
| fig = px.bar( | |
| x=brand_turnover.values, y=brand_turnover.index, | |
| orientation='h', | |
| title="Inventory Turnover by Brand", | |
| color=brand_turnover.values, | |
| color_continuous_scale='Viridis' | |
| ) | |
| apply_chart_style(fig, len(brand_turnover), is_horizontal=True) | |
| st.plotly_chart(fig, use_container_width=True) | |
| with col2: | |
| if brand_col: | |
| brand_is = df.groupby(brand_col)["IS_Ratio"].mean().sort_values() | |
| fig = px.bar( | |
| x=brand_is.index, y=brand_is.values, | |
| title="Inventory-to-Sales Ratio (Lower is Better)", | |
| color=brand_is.values, | |
| color_continuous_scale='RdYlGn_r' | |
| ) | |
| fig.update_layout( | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)', | |
| showlegend=False | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # Low turnover items | |
| st.markdown("#### ⚠️ Low Inventory Turnover (Risk)") | |
| worst = df.sort_values("Inventory_Turnover").head(10) | |
| display_cols = ["Inventory_Turnover"] | |
| if cat_col: display_cols.insert(0, cat_col) | |
| if brand_col: display_cols.insert(0, brand_col) | |
| st.dataframe(worst[display_cols], use_container_width=True) | |
| # ===================== | |
| # TAB 6: FORECAST | |
| # ===================== | |
| with tab6: | |
| st.markdown('<div class="section-header"><h3>🔮 Forecasting & Predictions</h3></div>', unsafe_allow_html=True) | |
| if date_col and sales_col: | |
| monthly = df.groupby(df[date_col].dt.to_period("M"))[sales_col].sum() | |
| monthly_values = monthly.values | |
| if len(monthly_values) >= 2: | |
| forecast_model = st.radio("Forecast Model", | |
| ["Linear Growth (Momentum)", "Moving Average (3-Month)"], | |
| horizontal=True) | |
| # Common variables | |
| last_month = monthly_values[-1] | |
| if forecast_model == "Moving Average (3-Month)": | |
| # Rolling Moving Average Forecast | |
| history = list(monthly_values) | |
| forecasted = [] | |
| for _ in range(3): | |
| # Average of last 3 periods | |
| next_val = np.mean(history[-3:]) | |
| forecasted.append(next_val) | |
| history.append(next_val) | |
| forecast_next, forecast_next_2, forecast_next_3 = forecasted | |
| avg_change = forecast_next - last_month # Implicit change | |
| else: | |
| # Original Linear Momentum Logic | |
| avg_change = np.mean(np.diff(monthly_values)) | |
| forecast_next = last_month + avg_change | |
| forecast_next_2 = last_month + (avg_change * 2) | |
| forecast_next_3 = last_month + (avg_change * 3) | |
| col1, col2, col3, col4 = st.columns(4) | |
| trend = "📈 GROWING" if avg_change > 0 else "📉 DECLINING" if avg_change < 0 else "➡️ STABLE" | |
| with col1: | |
| st.metric("Last Month", f"{last_month:,.0f}") | |
| with col2: | |
| st.metric("Avg Monthly Change", f"{avg_change:+,.0f}") | |
| with col3: | |
| st.metric("Next Month Forecast", f"{forecast_next:,.0f}") | |
| with col4: | |
| st.metric("Trend", trend) | |
| # Forecast visualization | |
| historical = list(monthly.values) | |
| forecasted = [forecast_next, forecast_next_2, forecast_next_3] | |
| all_periods = list(monthly.index.astype(str)) + ['M+1', 'M+2', 'M+3'] | |
| all_values = historical + forecasted | |
| colors = ['#667eea'] * len(historical) + ['#f093fb'] * 3 | |
| fig = go.Figure() | |
| fig.add_trace(go.Bar( | |
| x=all_periods, | |
| y=all_values, | |
| marker_color=colors | |
| )) | |
| fig.add_vline(x=len(historical)-0.5, line_dash="dash", line_color="white", opacity=0.5) | |
| fig.update_layout( | |
| title='Sales Forecast (Historical + Projected)', | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)' | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # Growth analysis | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| weekly = df.groupby(df[date_col].dt.isocalendar().week)[sales_col].sum() | |
| wow = weekly.pct_change() * 100 | |
| st.markdown("#### 📊 Growth Rate Analysis") | |
| st.metric("Avg WoW Growth", f"{wow.mean():.2f}%") | |
| st.metric("Best Week", f"+{wow.max():.2f}%") | |
| st.metric("Worst Week", f"{wow.min():.2f}%") | |
| with col2: | |
| # Price elasticity | |
| if price_col: | |
| price_corr = df[price_col].corr(df[sales_col]) | |
| if price_corr < -0.3: | |
| elasticity = "🔴 ELASTIC (Price Sensitive)" | |
| elif price_corr > 0.3: | |
| elasticity = "🟢 POSITIVE (Premium Effect)" | |
| else: | |
| elasticity = "🟡 INELASTIC (Price Insensitive)" | |
| st.markdown("#### 📈 Price Elasticity") | |
| st.metric("Price-Sales Correlation", f"{price_corr:.4f}") | |
| st.info(f"Demand Type: {elasticity}") | |
| # ===================== | |
| # TAB 7: ADVANCED | |
| # ===================== | |
| with tab7: | |
| st.markdown('<div class="section-header"><h3>📊 Advanced Analytics</h3></div>', unsafe_allow_html=True) | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| # Pareto Analysis | |
| if sales_col: | |
| st.markdown("#### 📊 Pareto Analysis (80/20 Rule)") | |
| sorted_sales = df.sort_values(sales_col, ascending=False).reset_index(drop=True) | |
| sorted_sales["Cumulative_Sales"] = sorted_sales[sales_col].cumsum() | |
| sorted_sales["Cumulative_Pct"] = sorted_sales["Cumulative_Sales"] / sorted_sales[sales_col].sum() * 100 | |
| products_for_80 = (sorted_sales["Cumulative_Pct"] <= 80).sum() | |
| pct_products = products_for_80 / len(df) * 100 | |
| st.metric("Products for 80% Revenue", f"{products_for_80:,}", f"{pct_products:.1f}% of total") | |
| fig = go.Figure() | |
| fig.add_trace(go.Scatter( | |
| y=sorted_sales["Cumulative_Pct"].values[:1000], | |
| mode='lines', | |
| fill='tozeroy', | |
| line=dict(color='#667eea') | |
| )) | |
| fig.add_hline(y=80, line_dash="dash", line_color="#f093fb") | |
| fig.update_layout( | |
| title='Cumulative Sales Distribution', | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)', | |
| yaxis_title='Cumulative %', | |
| xaxis_title='Products (ranked)' | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| with col2: | |
| # ABC Classification | |
| if sales_col: | |
| st.markdown("#### 🔤 ABC Classification") | |
| abc_df = df.groupby(df.index)[sales_col].sum().sort_values(ascending=False) | |
| abc_cumsum = abc_df.cumsum() / abc_df.sum() * 100 | |
| total = len(df) | |
| a_count = (abc_cumsum <= 70).sum() | |
| b_count = ((abc_cumsum > 70) & (abc_cumsum <= 90)).sum() | |
| c_count = (abc_cumsum > 90).sum() | |
| abc_data = pd.DataFrame({ | |
| 'Class': ['A (Top 70%)', 'B (Next 20%)', 'C (Bottom 10%)'], | |
| 'Items': [a_count, b_count, c_count], | |
| 'Percentage': [a_count/total*100, b_count/total*100, c_count/total*100] | |
| }) | |
| fig = px.pie( | |
| abc_data, values='Items', names='Class', | |
| title='ABC Classification', | |
| color_discrete_sequence=['#48bb78', '#ecc94b', '#f56565'] | |
| ) | |
| fig.update_layout( | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)' | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # Variance Analysis | |
| if sales_col: | |
| st.markdown("#### 📉 Variance Analysis") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| mean_val = df[sales_col].mean() | |
| overall_cv = (df[sales_col].std() / mean_val) * 100 if mean_val != 0 else 0 | |
| st.metric("Coefficient of Variation", f"{overall_cv:.1f}%", "Sales consistency") | |
| st.metric("Std Deviation", f"{df[sales_col].std():.2f}") | |
| with col2: | |
| if brand_col: | |
| def safe_cv(x): | |
| if x.mean() == 0: | |
| return 0 | |
| return (x.std() / x.mean()) * 100 | |
| brand_cv = df.groupby(brand_col)[sales_col].apply(safe_cv).sort_values() | |
| fig = px.bar( | |
| x=brand_cv.index, y=brand_cv.values, | |
| title="Variance by Brand (CV% - Lower = More Consistent)", | |
| color=brand_cv.values, | |
| color_continuous_scale='RdYlGn_r' | |
| ) | |
| fig.update_layout( | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)', | |
| showlegend=False | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # Correlation matrix | |
| st.markdown("#### 🔗 Correlation Analysis") | |
| num_cols = df.select_dtypes(np.number).columns[:10] # Limit to first 10 numeric columns | |
| corr_matrix = df[num_cols].corr() | |
| fig = px.imshow( | |
| corr_matrix, | |
| title="Correlation Heatmap", | |
| color_continuous_scale='RdBu_r', | |
| aspect='auto' | |
| ) | |
| fig.update_layout( | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)' | |
| ) | |
| st.plotly_chart(fig, width="stretch") | |
| # ===================== | |
| # TAB 8: COMPARISON | |
| # ===================== | |
| with tab8: | |
| st.markdown('<div class="section-header"><h3>🔄 Comparison Analysis</h3></div>', unsafe_allow_html=True) | |
| comparison_type = st.radio( | |
| "Compare by:", | |
| ["Brand vs Brand", "Category vs Category", "Time Period vs Time Period"], | |
| horizontal=True | |
| ) | |
| if comparison_type == "Brand vs Brand" and brand_col and sales_col: | |
| col1, col2 = st.columns(2) | |
| brands_list = sorted(df_original[brand_col].unique().tolist()) | |
| with col1: | |
| brand1 = st.selectbox("Select Brand 1", brands_list, key="brand1") | |
| with col2: | |
| brand2 = st.selectbox("Select Brand 2", brands_list, index=1 if len(brands_list) > 1 else 0, key="brand2") | |
| df1 = df_original[df_original[brand_col] == brand1] | |
| df2 = df_original[df_original[brand_col] == brand2] | |
| # Comparison metrics | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.markdown(f"### 🏷️ {brand1}") | |
| st.metric("Total Sales", f"{df1[sales_col].sum():,.0f}") | |
| st.metric("Avg Sale", f"{df1[sales_col].mean():.2f}") | |
| if "Profit_Margin" in df1.columns: | |
| st.metric("Avg Margin", f"{df1['Profit_Margin'].mean():.1f}%") | |
| if "CTR" in df1.columns: | |
| st.metric("Avg CTR", f"{df1['CTR'].mean():.2f}%") | |
| if "Ad_ROI" in df1.columns: | |
| st.metric("Avg ROI", f"{df1['Ad_ROI'].mean():.2f}") | |
| with col2: | |
| st.markdown(f"### 🏷️ {brand2}") | |
| st.metric("Total Sales", f"{df2[sales_col].sum():,.0f}") | |
| st.metric("Avg Sale", f"{df2[sales_col].mean():.2f}") | |
| if "Profit_Margin" in df2.columns: | |
| st.metric("Avg Margin", f"{df2['Profit_Margin'].mean():.1f}%") | |
| if "CTR" in df2.columns: | |
| st.metric("Avg CTR", f"{df2['CTR'].mean():.2f}%") | |
| if "Ad_ROI" in df2.columns: | |
| st.metric("Avg ROI", f"{df2['Ad_ROI'].mean():.2f}") | |
| # Comparison chart | |
| if date_col: | |
| comp_data = df_original[df_original[brand_col].isin([brand1, brand2])] | |
| monthly_comp = comp_data.groupby([comp_data[date_col].dt.to_period("M"), brand_col])[sales_col].sum().unstack() | |
| monthly_comp.index = monthly_comp.index.astype(str) | |
| fig = go.Figure() | |
| for brand in [brand1, brand2]: | |
| if brand in monthly_comp.columns: | |
| fig.add_trace(go.Scatter( | |
| x=monthly_comp.index, y=monthly_comp[brand], | |
| name=brand, mode='lines+markers' | |
| )) | |
| fig.update_layout( | |
| title=f"Sales Trend: {brand1} vs {brand2}", | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)' | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| elif comparison_type == "Category vs Category" and cat_col and sales_col: | |
| col1, col2 = st.columns(2) | |
| cats_list = sorted(df_original[cat_col].unique().tolist()) | |
| with col1: | |
| cat1 = st.selectbox("Select Category 1", cats_list, key="cat1") | |
| with col2: | |
| cat2 = st.selectbox("Select Category 2", cats_list, index=1 if len(cats_list) > 1 else 0, key="cat2") | |
| df1 = df_original[df_original[cat_col] == cat1] | |
| df2 = df_original[df_original[cat_col] == cat2] | |
| # Comparison metrics | |
| metrics_df = pd.DataFrame({ | |
| 'Metric': ['Total Sales', 'Avg Sale', 'Records'], | |
| cat1: [df1[sales_col].sum(), df1[sales_col].mean(), len(df1)], | |
| cat2: [df2[sales_col].sum(), df2[sales_col].mean(), len(df2)] | |
| }) | |
| st.dataframe(metrics_df, use_container_width=True) | |
| # Side by side bar chart | |
| fig = go.Figure(data=[ | |
| go.Bar(name=cat1, x=['Total Sales', 'Records'], y=[df1[sales_col].sum(), len(df1)]), | |
| go.Bar(name=cat2, x=['Total Sales', 'Records'], y=[df2[sales_col].sum(), len(df2)]) | |
| ]) | |
| fig.update_layout( | |
| title=f"{cat1} vs {cat2}", | |
| barmode='group', | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)' | |
| ) | |
| st.plotly_chart(fig, width="stretch") | |
| elif comparison_type == "Time Period vs Time Period" and date_col and sales_col: | |
| st.markdown("#### Select two time periods to compare") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.markdown("**Period 1**") | |
| p1_start = st.date_input("Start", value=df_original[date_col].min().date(), key="p1s") | |
| p1_end = st.date_input("End", value=df_original[date_col].min().date() + pd.Timedelta(days=30), key="p1e") | |
| with col2: | |
| st.markdown("**Period 2**") | |
| p2_start = st.date_input("Start", value=df_original[date_col].max().date() - pd.Timedelta(days=30), key="p2s") | |
| p2_end = st.date_input("End", value=df_original[date_col].max().date(), key="p2e") | |
| df1 = df_original[(df_original[date_col].dt.date >= p1_start) & (df_original[date_col].dt.date <= p1_end)] | |
| df2 = df_original[(df_original[date_col].dt.date >= p2_start) & (df_original[date_col].dt.date <= p2_end)] | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.markdown(f"### 📅 Period 1") | |
| st.caption(f"{p1_start} to {p1_end}") | |
| st.metric("Total Sales", f"{df1[sales_col].sum():,.0f}") | |
| st.metric("Daily Avg", f"{df1[sales_col].sum() / max(1, (p1_end - p1_start).days):,.0f}") | |
| st.metric("Records", f"{len(df1):,}") | |
| with col2: | |
| st.markdown(f"### 📅 Period 2") | |
| st.caption(f"{p2_start} to {p2_end}") | |
| p2_sales = df2[sales_col].sum() | |
| p1_sales = df1[sales_col].sum() | |
| change = ((p2_sales - p1_sales) / p1_sales * 100) if p1_sales > 0 else 0 | |
| st.metric("Total Sales", f"{p2_sales:,.0f}", f"{change:+.1f}%") | |
| st.metric("Daily Avg", f"{df2[sales_col].sum() / max(1, (p2_end - p2_start).days):,.0f}") | |
| st.metric("Records", f"{len(df2):,}") | |
| # ===================== | |
| # TAB 9: DATA EXPLORER | |
| # ===================== | |
| with tab9: | |
| st.markdown('<div class="section-header"><h3>🔍 Data Explorer</h3></div>', unsafe_allow_html=True) | |
| # Search and filter | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| search_col = st.selectbox("Search in column", df.columns.tolist()) | |
| with col2: | |
| search_term = st.text_input("Search term", "") | |
| with col3: | |
| rows_to_show = st.slider("Rows to display", 10, 500, 100) | |
| # Filter data | |
| if search_term: | |
| display_df = df[df[search_col].astype(str).str.contains(search_term, case=False, na=False)] | |
| else: | |
| display_df = df | |
| st.markdown(f"**Showing {min(rows_to_show, len(display_df)):,} of {len(display_df):,} records**") | |
| st.dataframe(display_df.head(rows_to_show), use_container_width=True) | |
| # Column statistics | |
| st.markdown("#### 📊 Column Statistics") | |
| stat_col = st.selectbox("Select column for statistics", df.columns.tolist()) | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| if df[stat_col].dtype in ['int64', 'float64']: | |
| stats = df[stat_col].describe() | |
| st.dataframe(stats, use_container_width=True) | |
| else: | |
| value_counts = df[stat_col].value_counts().head(10) | |
| st.markdown("**Top 10 Values**") | |
| st.dataframe(value_counts, use_container_width=True) | |
| with col2: | |
| if df[stat_col].dtype in ['int64', 'float64']: | |
| fig = px.histogram(df, x=stat_col, title=f"Distribution of {stat_col}", | |
| color_discrete_sequence=['#667eea']) | |
| else: | |
| value_counts = df[stat_col].value_counts().head(10) | |
| fig = px.bar(x=value_counts.index, y=value_counts.values, | |
| title=f"Top 10 {stat_col}", color_discrete_sequence=['#667eea']) | |
| fig.update_layout( | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)' | |
| ) | |
| st.plotly_chart(fig, width="stretch") | |
| # Correlation explorer | |
| st.markdown("#### 🔗 Quick Correlation Check") | |
| numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist() | |
| if len(numeric_cols) >= 2: | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| corr_col1 = st.selectbox("Column X", numeric_cols, key="corr1") | |
| with col2: | |
| corr_col2 = st.selectbox("Column Y", numeric_cols, index=1 if len(numeric_cols) > 1 else 0, key="corr2") | |
| corr_value = df[corr_col1].corr(df[corr_col2]) | |
| st.metric(f"Correlation: {corr_col1} vs {corr_col2}", f"{corr_value:.4f}") | |
| # Scatter plot (sample for performance) | |
| sample_df = df.sample(min(1000, len(df))) | |
| fig = px.scatter(sample_df, x=corr_col1, y=corr_col2, | |
| title=f"Scatter: {corr_col1} vs {corr_col2}", | |
| opacity=0.5) | |
| fig.update_layout( | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| plot_bgcolor='rgba(0,0,0,0)' | |
| ) | |
| st.plotly_chart(fig, width="stretch") | |
| # ===================== | |
| # TAB 10: GOALS & TARGETS | |
| # ===================== | |
| with tab10: | |
| st.markdown('<div class="section-header"><h3>🎯 Goals & Target Tracking</h3></div>', unsafe_allow_html=True) | |
| st.markdown("Set your business targets and track progress") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.markdown("#### 📊 Set Targets") | |
| if sales_col: | |
| sales_target = st.number_input("Sales Target", value=int(df[sales_col].sum() * 1.2), step=10000) | |
| margin_target = st.number_input("Profit Margin Target (%)", value=50.0, step=1.0) | |
| ctr_target = st.number_input("CTR Target (%)", value=12.0, step=0.5) | |
| roi_target = st.number_input("Ad ROI Target", value=1.0, step=0.1) | |
| with col2: | |
| st.markdown("#### 📈 Current Progress") | |
| if sales_col: | |
| current_sales = df[sales_col].sum() | |
| sales_progress = min(100, (current_sales / sales_target) * 100) if sales_target > 0 else 0 | |
| # Gauge chart for sales | |
| fig = go.Figure(go.Indicator( | |
| mode="gauge+number+delta", | |
| value=current_sales, | |
| delta={'reference': sales_target}, | |
| title={'text': "Sales Progress", 'font': {'color': 'white'}}, | |
| gauge={ | |
| 'axis': {'range': [0, sales_target * 1.2]}, | |
| 'bar': {'color': "#667eea"}, | |
| 'bgcolor': "rgba(255,255,255,0.1)", | |
| 'steps': [ | |
| {'range': [0, sales_target * 0.5], 'color': "#f56565"}, | |
| {'range': [sales_target * 0.5, sales_target * 0.8], 'color': "#ecc94b"}, | |
| {'range': [sales_target * 0.8, sales_target], 'color': "#48bb78"} | |
| ], | |
| 'threshold': { | |
| 'line': {'color': "white", 'width': 4}, | |
| 'thickness': 0.75, | |
| 'value': sales_target | |
| } | |
| } | |
| )) | |
| fig.update_layout( | |
| template='plotly_dark', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| height=250 | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # Progress bars for all targets | |
| st.markdown("#### 📋 Target Summary") | |
| targets_data = [] | |
| if sales_col: | |
| targets_data.append({ | |
| 'Metric': 'Sales', | |
| 'Target': f"{sales_target:,.0f}", | |
| 'Current': f"{df[sales_col].sum():,.0f}", | |
| 'Progress': f"{min(100, (df[sales_col].sum() / sales_target) * 100):.1f}%" if sales_target > 0 else "0%", | |
| 'Status': "✅" if df[sales_col].sum() >= sales_target else "⏳" | |
| }) | |
| if "Profit_Margin" in df.columns: | |
| current_margin = df['Profit_Margin'].mean() | |
| targets_data.append({ | |
| 'Metric': 'Profit Margin', | |
| 'Target': f"{margin_target:.1f}%", | |
| 'Current': f"{current_margin:.1f}%", | |
| 'Progress': f"{min(100, (current_margin / margin_target) * 100):.1f}%" if margin_target > 0 else "0%", | |
| 'Status': "✅" if current_margin >= margin_target else "⏳" | |
| }) | |
| if "CTR" in df.columns: | |
| current_ctr = df['CTR'].mean() | |
| targets_data.append({ | |
| 'Metric': 'CTR', | |
| 'Target': f"{ctr_target:.2f}%", | |
| 'Current': f"{current_ctr:.2f}%", | |
| 'Progress': f"{min(100, (current_ctr / ctr_target) * 100):.1f}%" if ctr_target > 0 else "0%", | |
| 'Status': "✅" if current_ctr >= ctr_target else "⏳" | |
| }) | |
| if "Ad_ROI" in df.columns: | |
| current_roi = df['Ad_ROI'].mean() | |
| targets_data.append({ | |
| 'Metric': 'Ad ROI', | |
| 'Target': f"{roi_target:.2f}", | |
| 'Current': f"{current_roi:.2f}", | |
| 'Progress': f"{min(100, (current_roi / roi_target) * 100):.1f}%" if roi_target > 0 else "0%", | |
| 'Status': "✅" if current_roi >= roi_target else "⏳" | |
| }) | |
| if targets_data: | |
| targets_df = pd.DataFrame(targets_data) | |
| st.dataframe(targets_df, use_container_width=True) | |
| # Recommendations based on gaps | |
| st.markdown("#### 💡 Recommendations to Hit Targets") | |
| recommendations = [] | |
| if sales_col and df[sales_col].sum() < sales_target: | |
| gap = sales_target - df[sales_col].sum() | |
| recommendations.append(f"📊 **Sales Gap**: Need {gap:,.0f} more sales. Consider increasing ad spend or running promotions.") | |
| if "Profit_Margin" in df.columns and df['Profit_Margin'].mean() < margin_target: | |
| recommendations.append("💰 **Margin Gap**: Focus on higher-margin products or reduce costs.") | |
| if "CTR" in df.columns and df['CTR'].mean() < ctr_target: | |
| recommendations.append("🖱️ **CTR Gap**: Improve ad creative and targeting to boost click-through rates.") | |
| if "Ad_ROI" in df.columns and df['Ad_ROI'].mean() < roi_target: | |
| recommendations.append("📢 **ROI Gap**: Optimize ad spend allocation - shift budget to better-performing campaigns.") | |
| if recommendations: | |
| for rec in recommendations: | |
| st.markdown(rec) | |
| else: | |
| st.success("🎉 Congratulations! You're meeting or exceeding all targets!") | |
| # ===================== | |
| # TAB 11: CONVERSION & POSITION | |
| # ===================== | |
| with tab11: | |
| st.markdown('<div class="section-header"><h3>🛒 Conversion & Position Analysis</h3></div>', unsafe_allow_html=True) | |
| if position_col: | |
| st.info("Search Position analysis has been moved to the Marketing tab.") | |
| if cart_col: | |
| st.markdown("---") | |
| st.markdown("#### 🛒 Cart Abandonment Analysis") | |
| total_carts = df[cart_col].sum() | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| st.metric("Total Abandoned Carts", f"{total_carts:,.0f}") | |
| with col2: | |
| avg_carts = df[cart_col].mean() | |
| st.metric("Avg Carts/Product", f"{avg_carts:.2f}") | |
| with col3: | |
| if price_col: | |
| lost_revenue = (df[cart_col] * df[price_col]).sum() | |
| st.metric("Est. Lost Revenue", f"${lost_revenue:,.0f}", help="Total Abandoned Carts * Price") | |
| # Sales vs Abandoned Carts Scatter | |
| if sales_col: | |
| sample_size = min(1000, len(df)) | |
| chart_df = df.sample(sample_size) | |
| fig = px.scatter( | |
| chart_df, x=cart_col, y=sales_col, | |
| title=f"Sales vs Abandoned Carts (Sample {sample_size})", | |
| opacity=0.6, | |
| trendline="ols" if sample_size > 10 else None | |
| ) | |
| apply_chart_style(fig) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # Correlation | |
| corr = df[sales_col].corr(df[cart_col]) | |
| st.info(f"💡 Correlation between Sales and Abandoned Carts: **{corr:.2f}**") | |
| # ===================== | |
| # TAB 12: AUTO INSIGHTS | |
| # ===================== | |
| with tab12: | |
| st.markdown('<div class="section-header"><h3>🤖 Auto Insight Engine</h3></div>', unsafe_allow_html=True) | |
| st.markdown("AI-powered analysis of your sales performance.") | |
| if st.button("🚀 Generate Insights"): | |
| with st.spinner("Analyzing data patterns..."): | |
| insights = auto_insight_engine(df, date_col, sales_col, inventory_col) | |
| if not insights: | |
| st.info("No significant anomalies or strong trends detected at this time.") | |
| else: | |
| for type_, title, msg in insights: | |
| if type_ == "success": | |
| st.success(f"**{title}**\n\n{msg}") | |
| elif type_ == "warning": | |
| st.warning(f"**{title}**\n\n{msg}") | |
| elif type_ == "error": | |
| st.error(f"**{title}**\n\n{msg}") | |
| else: | |
| st.info(f"**{title}**\n\n{msg}") | |
| # ===================== | |
| # EXECUTIVE SUMMARY | |
| # ===================== | |
| st.markdown('<div class="section-header"><h3>📝 Executive Summary</h3></div>', unsafe_allow_html=True) | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.markdown(""" | |
| <div class="info-box"> | |
| <h4 style="color: #667eea; margin-top: 0;">🎯 Key Insights</h4> | |
| <ul style="color: #e2e8f0;"> | |
| """, unsafe_allow_html=True) | |
| insights = [] | |
| if sales_col: | |
| insights.append(f"<li>Total Sales Volume: <strong>{df[sales_col].sum():,.0f}</strong></li>") | |
| if "Profit_Margin" in df.columns: | |
| insights.append(f"<li>Average Profit Margin: <strong>{df['Profit_Margin'].mean():.1f}%</strong></li>") | |
| if "Ad_ROI" in df.columns: | |
| insights.append(f"<li>Average Ad ROI: <strong>{df['Ad_ROI'].mean():.2f}</strong> sales per dollar</li>") | |
| if date_col and sales_col: | |
| monthly = df.groupby(df[date_col].dt.to_period("M"))[sales_col].sum() | |
| volatility = monthly.pct_change().abs().mean() * 100 | |
| insights.append(f"<li>Monthly Volatility: <strong>{volatility:.1f}%</strong></li>") | |
| st.markdown("".join(insights) + "</ul></div>", unsafe_allow_html=True) | |
| with col2: | |
| st.markdown(""" | |
| <div class="success-box"> | |
| <h4 style="color: #48bb78; margin-top: 0;">✅ Recommendations</h4> | |
| <ol style="color: #e2e8f0;"> | |
| <li>Focus on high-margin brands for profitability</li> | |
| <li>Reallocate marketing budget based on ROI</li> | |
| <li>Address low inventory turnover items</li> | |
| <li>Optimize pricing for price-sensitive segments</li> | |
| <li>Focus on Class A products for maximum impact</li> | |
| </ol> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # ===================== | |
| # DATA EXPORT | |
| # ===================== | |
| st.markdown('<div class="section-header"><h3>📥 Export Data</h3></div>', unsafe_allow_html=True) | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| if st.button("📊 Download Summary CSV", use_container_width=True): | |
| summary_data = { | |
| "Metric": ["Total Sales", "Total Records", "Avg Profit Margin", "Avg CTR", "Avg Ad ROI"], | |
| "Value": [ | |
| df[sales_col].sum() if sales_col else 0, | |
| len(df), | |
| df["Profit_Margin"].mean() if "Profit_Margin" in df.columns else 0, | |
| df["CTR"].mean() if "CTR" in df.columns else 0, | |
| df["Ad_ROI"].mean() if "Ad_ROI" in df.columns else 0 | |
| ] | |
| } | |
| summary_df = pd.DataFrame(summary_data) | |
| st.download_button( | |
| "⬇️ Download", | |
| summary_df.to_csv(index=False), | |
| "analysis_summary.csv", | |
| "text/csv" | |
| ) | |
| with col2: | |
| if brand_col and sales_col: | |
| if st.button("🏷️ Download Brand Report", use_container_width=True): | |
| brand_report = df.groupby(brand_col).agg({sales_col: 'sum'}).round(2) | |
| if "Profit_Margin" in df.columns: | |
| brand_report["Avg_Margin"] = df.groupby(brand_col)["Profit_Margin"].mean() | |
| if "Ad_ROI" in df.columns: | |
| brand_report["Avg_ROI"] = df.groupby(brand_col)["Ad_ROI"].mean() | |
| st.download_button( | |
| "⬇️ Download", | |
| brand_report.to_csv(), | |
| "brand_analysis.csv", | |
| "text/csv" | |
| ) | |
| with col3: | |
| if st.button("📋 Download Full Data", use_container_width=True): | |
| st.download_button( | |
| "⬇️ Download", | |
| df.to_csv(index=False), | |
| "full_data_with_metrics.csv", | |
| "text/csv" | |
| ) | |
| # Footer | |
| st.markdown(""" | |
| <div style="text-align: center; padding: 40px 0 20px 0; color: #718096;"> | |
| <p>Built with ❤️ using Streamlit | Sales Insight Dashboard v1.0</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |