| | import streamlit as st
|
| | import pandas as pd
|
| | import numpy as np
|
| | import plotly.express as px
|
| | import plotly.graph_objects as go
|
| | from datetime import datetime, timedelta
|
| | from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
|
| | from sklearn.model_selection import train_test_split
|
| | from sklearn.preprocessing import StandardScaler
|
| | from sklearn.cluster import KMeans
|
| | import warnings
|
| | warnings.filterwarnings('ignore')
|
| |
|
| |
|
| | st.set_page_config(
|
| | page_title="Calcium Supplement Sales Automation",
|
| | page_icon="๐",
|
| | layout="wide",
|
| | initial_sidebar_state="expanded"
|
| | )
|
| |
|
| |
|
| | st.title("๐ Calcium Supplement Sales Automation Dashboard")
|
| | st.markdown("---")
|
| |
|
| |
|
| | def enhanced_analyze_sales_data(data1, data2):
|
| | """
|
| | Enhanced analysis with ML components for better predictions
|
| | """
|
| |
|
| | data1['Date'] = pd.to_datetime(data1['Date'])
|
| | data2['Date'] = pd.to_datetime(data2['Date'])
|
| |
|
| |
|
| | data1['Conversion_Rate'] = (data1['Contact_In_Group'] / data1['Sabhasad'] * 100).round(2)
|
| | data1['Conversion_Rate'] = data1['Conversion_Rate'].replace([np.inf, -np.inf], 0).fillna(0)
|
| | data1['Untapped_Potential'] = data1['Sabhasad'] - data1['Contact_In_Group']
|
| | data1['Sales_Per_Contact'] = (data1['Total_L'] / data1['Contact_In_Group']).round(2)
|
| | data1['Sales_Per_Contact'] = data1['Sales_Per_Contact'].replace([np.inf, -np.inf], 0).fillna(0)
|
| |
|
| |
|
| | recent_sales = data2.groupby('Village').agg({
|
| | 'Total_L': ['sum', 'count'],
|
| | 'Date': 'max'
|
| | }).reset_index()
|
| |
|
| | recent_sales.columns = ['Village', 'Recent_Sales_L', 'Recent_Customers', 'Last_Sale_Date']
|
| | recent_sales['Days_Since_Last_Sale'] = (datetime.now() - recent_sales['Last_Sale_Date']).dt.days
|
| |
|
| |
|
| | analysis_df = data1.merge(recent_sales, on='Village', how='left')
|
| | analysis_df['Recent_Sales_L'] = analysis_df['Recent_Sales_L'].fillna(0)
|
| | analysis_df['Recent_Customers'] = analysis_df['Recent_Customers'].fillna(0)
|
| | analysis_df['Days_Since_Last_Sale'] = analysis_df['Days_Since_Last_Sale'].fillna(999)
|
| |
|
| |
|
| | analysis_df = apply_village_clustering(analysis_df)
|
| |
|
| |
|
| | analysis_df = predict_sales_potential(analysis_df)
|
| |
|
| |
|
| | analysis_df = predict_recommended_actions(analysis_df)
|
| |
|
| |
|
| | recommendations = generate_ml_recommendations(analysis_df)
|
| |
|
| | return recommendations, analysis_df
|
| |
|
| | def apply_village_clustering(analysis_df):
|
| | """
|
| | Use K-Means clustering to segment villages into groups
|
| | """
|
| |
|
| | cluster_features = analysis_df[[
|
| | 'Conversion_Rate', 'Untapped_Potential', 'Sales_Per_Contact',
|
| | 'Recent_Sales_L', 'Days_Since_Last_Sale'
|
| | ]].fillna(0)
|
| |
|
| |
|
| | scaler = StandardScaler()
|
| | scaled_features = scaler.fit_transform(cluster_features)
|
| |
|
| |
|
| | kmeans = KMeans(n_clusters=4, random_state=42, n_init=10)
|
| | clusters = kmeans.fit_predict(scaled_features)
|
| |
|
| |
|
| | analysis_df['Cluster'] = clusters
|
| |
|
| |
|
| | cluster_names = {
|
| | 0: 'High Potential - Low Engagement',
|
| | 1: 'Steady Performers',
|
| | 2: 'Underperforming',
|
| | 3: 'New/Developing'
|
| | }
|
| |
|
| | analysis_df['Segment'] = analysis_df['Cluster'].map(cluster_names)
|
| |
|
| | return analysis_df
|
| |
|
| | def predict_sales_potential(analysis_df):
|
| | """
|
| | Predict sales potential for each village using Random Forest
|
| | """
|
| |
|
| | prediction_features = analysis_df[[
|
| | 'Sabhasad', 'Contact_In_Group', 'Conversion_Rate',
|
| | 'Untapped_Potential', 'Recent_Sales_L', 'Days_Since_Last_Sale'
|
| | ]].fillna(0)
|
| |
|
| |
|
| | target = analysis_df['Total_L'].fillna(0)
|
| |
|
| |
|
| | if len(prediction_features) > 10:
|
| |
|
| | X_train, X_test, y_train, y_test = train_test_split(
|
| | prediction_features, target, test_size=0.2, random_state=42
|
| | )
|
| |
|
| |
|
| | model = RandomForestRegressor(n_estimators=100, random_state=42)
|
| | model.fit(X_train, y_train)
|
| |
|
| |
|
| | predictions = model.predict(prediction_features)
|
| |
|
| |
|
| | feature_importance = pd.DataFrame({
|
| | 'feature': prediction_features.columns,
|
| | 'importance': model.feature_importances_
|
| | }).sort_values('importance', ascending=False)
|
| |
|
| |
|
| | analysis_df['Predicted_Sales'] = predictions
|
| | analysis_df['Sales_Gap'] = analysis_df['Predicted_Sales'] - analysis_df['Total_L']
|
| | else:
|
| |
|
| | analysis_df['Predicted_Sales'] = analysis_df['Total_L']
|
| | analysis_df['Sales_Gap'] = 0
|
| |
|
| | return analysis_df
|
| |
|
| | def predict_recommended_actions(analysis_df):
|
| | """
|
| | Use ML to predict the best action for each village
|
| | """
|
| |
|
| | analysis_df['Action_Label'] = np.where(
|
| | analysis_df['Conversion_Rate'] < 20, 'Send Marketing Team',
|
| | np.where(
|
| | analysis_df['Untapped_Potential'] > 30, 'Call Mantri for Follow-up',
|
| | np.where(
|
| | analysis_df['Days_Since_Last_Sale'] > 30, 'Check on Mantri',
|
| | np.where(
|
| | analysis_df['Sales_Per_Contact'] > 10, 'Provide More Stock',
|
| | 'Regular Follow-up'
|
| | )
|
| | )
|
| | )
|
| | )
|
| |
|
| |
|
| | classification_features = analysis_df[[
|
| | 'Conversion_Rate', 'Untapped_Potential', 'Sales_Per_Contact',
|
| | 'Recent_Sales_L', 'Days_Since_Last_Sale', 'Sales_Gap'
|
| | ]].fillna(0)
|
| |
|
| |
|
| | target = analysis_df['Action_Label']
|
| |
|
| |
|
| | if len(classification_features) > 10 and len(target.unique()) > 1:
|
| |
|
| | X_train, X_test, y_train, y_test = train_test_split(
|
| | classification_features, target, test_size=0.2, random_state=42, stratify=target
|
| | )
|
| |
|
| |
|
| | clf = RandomForestClassifier(n_estimators=100, random_state=42)
|
| | clf.fit(X_train, y_train)
|
| |
|
| |
|
| | predictions = clf.predict(classification_features)
|
| | prediction_proba = clf.predict_proba(classification_features)
|
| |
|
| |
|
| | analysis_df['ML_Recommended_Action'] = predictions
|
| | analysis_df['Action_Confidence'] = np.max(prediction_proba, axis=1)
|
| | else:
|
| |
|
| | analysis_df['ML_Recommended_Action'] = analysis_df['Action_Label']
|
| | analysis_df['Action_Confidence'] = 1.0
|
| |
|
| | return analysis_df
|
| |
|
| | def generate_ml_recommendations(analysis_df):
|
| | """
|
| | Generate recommendations based on ML predictions
|
| | """
|
| | recommendations = []
|
| |
|
| | for _, row in analysis_df.iterrows():
|
| | village = row['Village']
|
| | mantri = row['Mantri_Name']
|
| | mobile = row['Mantri_Mobile']
|
| | taluka = row['Taluka']
|
| | district = row['District']
|
| | segment = row['Segment']
|
| | action = row['ML_Recommended_Action']
|
| | confidence = row['Action_Confidence']
|
| |
|
| |
|
| | if action == 'Send Marketing Team':
|
| | reason = f"ML predicts marketing team needed (Confidence: {confidence:.2f}). Segment: {segment}"
|
| | priority = 'High'
|
| | elif action == 'Call Mantri for Follow-up':
|
| | reason = f"ML predicts mantri follow-up needed (Confidence: {confidence:.2f}). Segment: {segment}"
|
| | priority = 'High'
|
| | elif action == 'Check on Mantri':
|
| | reason = f"ML suggests checking on mantri (Confidence: {confidence:.2f}). Segment: {segment}"
|
| | priority = 'Medium'
|
| | elif action == 'Provide More Stock':
|
| | reason = f"ML predicts stock increase needed (Confidence: {confidence:.2f}). Segment: {segment}"
|
| | priority = 'Medium'
|
| | else:
|
| | reason = f"ML recommends regular follow-up (Confidence: {confidence:.2f}). Segment: {segment}"
|
| | priority = 'Low'
|
| |
|
| | recommendations.append({
|
| | 'Village': village,
|
| | 'Taluka': taluka,
|
| | 'District': district,
|
| | 'Mantri': mantri,
|
| | 'Mobile': mobile,
|
| | 'Action': action,
|
| | 'Reason': reason,
|
| | 'Priority': priority,
|
| | 'Confidence': confidence,
|
| | 'Segment': segment,
|
| | 'Sales_Gap': row.get('Sales_Gap', 0)
|
| | })
|
| |
|
| | return pd.DataFrame(recommendations)
|
| |
|
| | def generate_ml_mantri_messages(recommendations):
|
| | """
|
| | Generate personalized messages based on ML recommendations
|
| | """
|
| | messages = []
|
| |
|
| | for _, row in recommendations.iterrows():
|
| | if row['Action'] == 'Send Marketing Team':
|
| | message = f"""
|
| | Namaste {row['Mantri']} Ji!
|
| |
|
| | Our AI system has identified that your village {row['Village']} has high potential for growth.
|
| | We're sending our marketing team to conduct demo sessions and help you reach more customers.
|
| |
|
| | Based on our analysis:
|
| | - Segment: {row['Segment']}
|
| | - Confidence: {row['Confidence']*100:.1f}%
|
| |
|
| | Please prepare for their visit and notify potential customers.
|
| |
|
| | Dhanyavaad,
|
| | Calcium Supplement Team
|
| | """
|
| | elif row['Action'] == 'Call Mantri for Follow-up':
|
| | message = f"""
|
| | Namaste {row['Mantri']} Ji!
|
| |
|
| | Our AI analysis shows significant untapped potential in {row['Village']}.
|
| | We recommend focusing on follow-up with these customers:
|
| |
|
| | - Segment: {row['Segment']}
|
| | - Confidence: {row['Confidence']*100:.1f}%
|
| |
|
| | A special commission offer is available for your next 10 customers.
|
| |
|
| | Dhanyavaad,
|
| | Calcium Supplement Team
|
| | """
|
| | elif row['Action'] == 'Check on Mantri':
|
| | message = f"""
|
| | Namaste {row['Mantri']} Ji!
|
| |
|
| | Our system shows reduced activity in {row['Village']}.
|
| | Is everything alright? Do you need any support from our team?
|
| |
|
| | - Segment: {row['Segment']}
|
| | - Confidence: {row['Confidence']*100:.1f}%
|
| |
|
| | Please let us know how we can help.
|
| |
|
| | Dhanyavaad,
|
| | Calcium Supplement Team
|
| | """
|
| | elif row['Action'] == 'Provide More Stock':
|
| | message = f"""
|
| | Namaste {row['Mantri']} Ji!
|
| |
|
| | Great news! Our AI predicts increased demand in {row['Village']}.
|
| | Would you like us to send additional stock?
|
| |
|
| | - Segment: {row['Segment']}
|
| | - Confidence: {row['Confidence']*100:.1f}%
|
| | - Predicted Sales Gap: {row['Sales_Gap']:.1f}L
|
| |
|
| | Please confirm your additional requirements.
|
| |
|
| | Dhanyavaad,
|
| | Calcium Supplement Team
|
| | """
|
| | else:
|
| | message = f"""
|
| | Namaste {row['Mantri']} Ji!
|
| |
|
| | Our system shows steady performance in {row['Village']}.
|
| | Keep up the good work!
|
| |
|
| | - Segment: {row['Segment']}
|
| | - Confidence: {row['Confidence']*100:.1f}%
|
| |
|
| | As always, let us know if you need any support.
|
| |
|
| | Dhanyavaad,
|
| | Calcium Supplement Team
|
| | """
|
| |
|
| | messages.append({
|
| | 'Mantri': row['Mantri'],
|
| | 'Mobile': row['Mobile'],
|
| | 'Village': row['Village'],
|
| | 'Action': row['Action'],
|
| | 'Message': message,
|
| | 'Priority': row['Priority'],
|
| | 'Confidence': row['Confidence']
|
| | })
|
| |
|
| | return pd.DataFrame(messages)
|
| |
|
| |
|
| | def plot_village_performance(analysis_df):
|
| | """Create performance visualization for villages"""
|
| | fig = px.scatter(analysis_df,
|
| | x='Conversion_Rate',
|
| | y='Untapped_Potential',
|
| | size='Total_L',
|
| | color='Segment',
|
| | hover_name='Village',
|
| | title='Village Performance Analysis',
|
| | labels={'Conversion_Rate': 'Conversion Rate (%)',
|
| | 'Untapped_Potential': 'Untapped Potential'})
|
| |
|
| | fig.update_layout(height=500)
|
| | return fig
|
| |
|
| | def plot_sales_trends(analysis_df):
|
| | """Create sales trends visualization"""
|
| | fig = px.bar(analysis_df,
|
| | x='Village',
|
| | y='Total_L',
|
| | color='Segment',
|
| | title='Total Sales by Village',
|
| | labels={'Total_L': 'Total Sales (L)', 'Village': 'Village'})
|
| |
|
| | fig.update_layout(height=400, xaxis_tickangle=-45)
|
| | return fig
|
| |
|
| | def plot_priority_matrix(recommendations):
|
| | """Create priority matrix visualization"""
|
| | priority_order = {'High': 3, 'Medium': 2, 'Low': 1}
|
| | recommendations['Priority_Value'] = recommendations['Priority'].map(priority_order)
|
| |
|
| | fig = px.treemap(recommendations,
|
| | path=['Priority', 'Village'],
|
| | values='Priority_Value',
|
| | color='Priority_Value',
|
| | color_continuous_scale='RdYlGn_r',
|
| | title='Action Priority Matrix')
|
| |
|
| | fig.update_layout(height=500)
|
| | return fig
|
| |
|
| | def display_key_metrics(analysis_df):
|
| | """Display key performance metrics"""
|
| | col1, col2, col3, col4 = st.columns(4)
|
| |
|
| | with col1:
|
| | st.metric("Total Villages", len(analysis_df))
|
| | with col2:
|
| | avg_conversion = analysis_df['Conversion_Rate'].mean()
|
| | st.metric("Avg Conversion Rate", f"{avg_conversion:.1f}%")
|
| | with col3:
|
| | total_untapped = analysis_df['Untapped_Potential'].sum()
|
| | st.metric("Total Untapped Potential", f"{total_untapped}")
|
| | with col4:
|
| | total_sales = analysis_df['Total_L'].sum()
|
| | st.metric("Total Sales (L)", f"{total_sales}")
|
| |
|
| |
|
| | if 'data1' not in st.session_state:
|
| | st.session_state.data1 = None
|
| | if 'data2' not in st.session_state:
|
| | st.session_state.data2 = None
|
| | if 'analysis_df' not in st.session_state:
|
| | st.session_state.analysis_df = None
|
| | if 'recommendations' not in st.session_state:
|
| | st.session_state.recommendations = None
|
| | if 'ml_messages' not in st.session_state:
|
| | st.session_state.ml_messages = None
|
| |
|
| |
|
| | with st.sidebar:
|
| | st.header("Data Input")
|
| |
|
| |
|
| | st.subheader("Upload Village Data (Data1)")
|
| | uploaded_data1 = st.file_uploader("CSV or Excel file", type=["csv", "xlsx"], key="data1")
|
| |
|
| | st.subheader("Upload Sales Data (Data2)")
|
| | uploaded_data2 = st.file_uploader("CSV or Excel file", type=["csv", "xlsx"], key="data2")
|
| |
|
| | if st.button("Load Data and Run ML Analysis"):
|
| | if uploaded_data1 and uploaded_data2:
|
| | try:
|
| |
|
| | if uploaded_data1.name.endswith('.csv'):
|
| | data1 = pd.read_csv(uploaded_data1)
|
| | else:
|
| | data1 = pd.read_excel(uploaded_data1)
|
| |
|
| | if uploaded_data2.name.endswith('.csv'):
|
| | data2 = pd.read_csv(uploaded_data2)
|
| | else:
|
| | data2 = pd.read_excel(uploaded_data2)
|
| |
|
| |
|
| | st.session_state.data1 = data1
|
| | st.session_state.data2 = data2
|
| |
|
| |
|
| | with st.spinner("Running ML analysis..."):
|
| | recommendations, analysis_df = enhanced_analyze_sales_data(data1, data2)
|
| | st.session_state.analysis_df = analysis_df
|
| | st.session_state.recommendations = recommendations
|
| |
|
| | ml_messages = generate_ml_mantri_messages(recommendations)
|
| | st.session_state.ml_messages = ml_messages
|
| |
|
| | st.success("ML analysis completed successfully!")
|
| |
|
| | except Exception as e:
|
| | st.error(f"Error processing data: {str(e)}")
|
| | else:
|
| | st.error("Please upload both files to proceed")
|
| |
|
| |
|
| | if st.session_state.analysis_df is not None and st.session_state.recommendations is not None:
|
| |
|
| | tab1, tab2, tab3, tab4 = st.tabs(["Dashboard", "Village Analysis", "Actions & Messages", "Team Dispatch"])
|
| |
|
| | with tab1:
|
| | st.header("ML-Powered Performance Dashboard")
|
| | display_key_metrics(st.session_state.analysis_df)
|
| |
|
| | col1, col2 = st.columns(2)
|
| |
|
| | with col1:
|
| | st.plotly_chart(plot_village_performance(st.session_state.analysis_df), use_container_width=True)
|
| |
|
| | with col2:
|
| | st.plotly_chart(plot_priority_matrix(st.session_state.recommendations), use_container_width=True)
|
| |
|
| | st.plotly_chart(plot_sales_trends(st.session_state.analysis_df), use_container_width=True)
|
| |
|
| | with tab2:
|
| | st.header("Village Analysis with ML Segmentation")
|
| |
|
| | selected_village = st.selectbox("Select Village", st.session_state.analysis_df['Village'].unique())
|
| | village_data = st.session_state.analysis_df[st.session_state.analysis_df['Village'] == selected_village].iloc[0]
|
| |
|
| | col1, col2 = st.columns(2)
|
| |
|
| | with col1:
|
| | st.subheader("Village Details")
|
| | st.write(f"**Village:** {village_data['Village']}")
|
| | st.write(f"**Taluka:** {village_data['Taluka']}")
|
| | st.write(f"**District:** {village_data['District']}")
|
| | st.write(f"**Mantri:** {village_data['Mantri_Name']}")
|
| | st.write(f"**Mantri Mobile:** {village_data['Mantri_Mobile']}")
|
| | st.write(f"**Segment:** {village_data.get('Segment', 'N/A')}")
|
| | st.write(f"**ML Recommended Action:** {village_data.get('ML_Recommended_Action', 'N/A')}")
|
| | st.write(f"**Action Confidence:** {village_data.get('Action_Confidence', 'N/A'):.2f}")
|
| |
|
| | with col2:
|
| | st.subheader("Performance Metrics")
|
| | st.write(f"**Sabhasad:** {village_data['Sabhasad']}")
|
| | st.write(f"**Contacted:** {village_data['Contact_In_Group']}")
|
| | st.write(f"**Conversion Rate:** {village_data['Conversion_Rate']}%")
|
| | st.write(f"**Untapped Potential:** {village_data['Untapped_Potential']}")
|
| | st.write(f"**Total Sales:** {village_data['Total_L']}L")
|
| | st.write(f"**Sales per Contact:** {village_data['Sales_Per_Contact']}L")
|
| | st.write(f"**Predicted Sales:** {village_data.get('Predicted_Sales', 'N/A'):.1f}L")
|
| | st.write(f"**Sales Gap:** {village_data.get('Sales_Gap', 'N/A'):.1f}L")
|
| |
|
| | with tab3:
|
| | st.header("ML-Based Actions & Messages")
|
| |
|
| | st.subheader("ML-Generated Recommendations")
|
| | st.dataframe(st.session_state.recommendations)
|
| |
|
| |
|
| | csv_data = st.session_state.recommendations.to_csv(index=False)
|
| | st.download_button(
|
| | label="Download Recommendations as CSV",
|
| | data=csv_data,
|
| | file_name="ml_sales_recommendations.csv",
|
| | mime="text/csv"
|
| | )
|
| |
|
| | st.subheader("Generate ML-Powered Messages")
|
| | selected_mantri = st.selectbox("Select Mantri", st.session_state.recommendations['Mantri'].unique())
|
| | mantri_data = st.session_state.recommendations[
|
| | st.session_state.recommendations['Mantri'] == selected_mantri].iloc[0]
|
| |
|
| | message_df = st.session_state.ml_messages[
|
| | st.session_state.ml_messages['Mantri'] == selected_mantri]
|
| |
|
| | if not message_df.empty:
|
| | message = message_df.iloc[0]['Message']
|
| | st.text_area("ML-Generated Message", message, height=300)
|
| |
|
| | if st.button("Send Message"):
|
| | st.success(f"Message sent to {mantri_data['Mantri']} at {mantri_data['Mobile']}")
|
| |
|
| | st.subheader("Bulk Message Sender")
|
| | if st.button("Generate All ML Messages"):
|
| | st.session_state.all_messages = st.session_state.ml_messages
|
| |
|
| | if 'all_messages' in st.session_state:
|
| | st.dataframe(st.session_state.all_messages[['Mantri', 'Village', 'Action', 'Priority', 'Confidence']])
|
| |
|
| | if st.button("Send All ML Messages"):
|
| | progress_bar = st.progress(0)
|
| | for i, row in st.session_state.all_messages.iterrows():
|
| |
|
| | progress_bar.progress((i + 1) / len(st.session_state.all_messages))
|
| | st.success("All ML-powered messages sent successfully!")
|
| |
|
| | with tab4:
|
| | st.header("Marketing Team Dispatch with ML Insights")
|
| |
|
| | st.subheader("Villages Needing Team Visit (ML Identified)")
|
| | high_priority = st.session_state.recommendations[
|
| | st.session_state.recommendations['Action'] == 'Send Marketing Team']
|
| |
|
| | if not high_priority.empty:
|
| | for _, row in high_priority.iterrows():
|
| | with st.expander(f"{row['Village']} - {row['Mantri']} (Confidence: {row['Confidence']:.2f})"):
|
| | st.write(f"**Reason:** {row['Reason']}")
|
| | st.write(f"**Segment:** {row['Segment']}")
|
| | st.write(f"**Sales Gap:** {row['Sales_Gap']:.1f}L")
|
| |
|
| | dispatch_date = st.date_input("Dispatch Date", key=f"date_{row['Village']}")
|
| | team_size = st.slider("Team Size", 1, 5, 2, key=f"size_{row['Village']}")
|
| |
|
| | if st.button("Schedule Dispatch", key=f"dispatch_{row['Village']}"):
|
| | st.success(f"Team dispatch scheduled for {row['Village']} on {dispatch_date}")
|
| | else:
|
| | st.info("No villages currently require immediate team dispatch based on ML analysis.")
|
| |
|
| | st.subheader("ML Performance Insights")
|
| | st.write("Based on our machine learning analysis, here are key insights:")
|
| |
|
| |
|
| | segment_counts = st.session_state.analysis_df['Segment'].value_counts()
|
| | fig = px.pie(values=segment_counts.values, names=segment_counts.index,
|
| | title="Village Segment Distribution")
|
| | st.plotly_chart(fig, use_container_width=True)
|
| |
|
| |
|
| | fig = px.histogram(st.session_state.recommendations, x='Confidence',
|
| | title='Confidence Distribution of ML Recommendations')
|
| | st.plotly_chart(fig, use_container_width=True)
|
| |
|
| | else:
|
| | st.info("Please upload your data files using the sidebar and click 'Load Data and Run ML Analysis' to get started.")
|
| |
|
| |
|
| | st.markdown("---")
|
| | st.markdown("**ML-Powered Calcium Supplement Sales Automation System** | For internal use only") |