omgy commited on
Commit
97000ce
·
verified ·
1 Parent(s): e26493f

Upload 52 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. OLD/Analysis.ipynb +0 -0
  2. OLD/Chatbot.py +52 -0
  3. OLD/Main.py +244 -0
  4. OLD/__dbmlsystem.py +596 -0
  5. OLD/sampleDashboard.py +350 -0
  6. data/AMBERAVPURA ENGLISH SABHASAD LIST.xlsx +0 -0
  7. data/APRIL 24-25.xlsx +0 -0
  8. data/AUGUST 24-25.xlsx +0 -0
  9. data/JULY 24-25.xlsx +0 -0
  10. data/JUNE 24-25.xlsx +0 -0
  11. data/MAY 24-25.xlsx +0 -0
  12. data/SEPTEMBER 24-25.xlsx +0 -0
  13. data/amiyad.xlsx +0 -0
  14. data/dharkhuniya.xlsx +0 -0
  15. data/distributors.xlsx +0 -0
  16. data/kamrol.xlsx +0 -0
  17. data/sandha.xlsx +0 -0
  18. data/vishnoli.xlsx +0 -0
  19. pages/__init__.py +0 -0
  20. pages/__pycache__/__init__.cpython-310.pyc +0 -0
  21. pages/__pycache__/__init__.cpython-313.pyc +0 -0
  22. pages/__pycache__/customers.cpython-310.pyc +0 -0
  23. pages/__pycache__/dashboard.cpython-310.pyc +0 -0
  24. pages/__pycache__/dashboard.cpython-313.pyc +0 -0
  25. pages/__pycache__/data_import.cpython-310.pyc +0 -0
  26. pages/__pycache__/demos.cpython-310.pyc +0 -0
  27. pages/__pycache__/distributors.cpython-310.pyc +0 -0
  28. pages/__pycache__/file_viewer.cpython-310.pyc +0 -0
  29. pages/__pycache__/payments.cpython-310.pyc +0 -0
  30. pages/__pycache__/reports.cpython-310.pyc +0 -0
  31. pages/__pycache__/sales.cpython-310.pyc +0 -0
  32. pages/__pycache__/system_dashboard.cpython-310.pyc +0 -0
  33. pages/customers.py +449 -0
  34. pages/dashboard.py +12 -0
  35. pages/data_import.py +104 -0
  36. pages/demos.py +824 -0
  37. pages/distributors.py +971 -0
  38. pages/file_viewer.py +382 -0
  39. pages/payments.py +524 -0
  40. pages/reports.py +1101 -0
  41. pages/sales.py +500 -0
  42. pages/system_dashboard.py +137 -0
  43. pages/whatsapp.py +68 -0
  44. utils/__init__.py +0 -0
  45. utils/__pycache__/__init__.cpython-310.pyc +0 -0
  46. utils/__pycache__/__init__.cpython-313.pyc +0 -0
  47. utils/__pycache__/helpers.cpython-310.pyc +0 -0
  48. utils/__pycache__/helpers.cpython-313.pyc +0 -0
  49. utils/__pycache__/styling.cpython-310.pyc +0 -0
  50. utils/__pycache__/styling.cpython-313.pyc +0 -0
OLD/Analysis.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
OLD/Chatbot.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import requests
4
+
5
+ # ---- Load sample data ----
6
+ @st.cache_data
7
+ def load_data():
8
+ return pd.read_csv("data.csv")
9
+
10
+ df = load_data()
11
+
12
+ # ---- Sidebar ----
13
+ st.sidebar.title("Controls")
14
+ task = st.sidebar.selectbox("Choose task", ["Chat with Bot (via n8n)", "Analyze Data"])
15
+
16
+ # ---- Main UI ----
17
+ st.title("💬 Data Analysis Assistant (Streamlit + n8n + Ollama)")
18
+
19
+ if task == "Analyze Data":
20
+ st.subheader("📊 Sales Data")
21
+ st.dataframe(df)
22
+
23
+ st.write("### Total Sales:")
24
+ st.metric("💵 Amount", f"${df['amount'].sum():,.2f}")
25
+
26
+ top_customer = df.groupby("customer")["amount"].sum().idxmax()
27
+ st.write(f"**Top Customer:** {top_customer}")
28
+
29
+ elif task == "Chat with Bot (via n8n)":
30
+ st.subheader("🤖 Ask Questions")
31
+ user_input = st.text_area("Your question:", placeholder="e.g. Who spent the most?")
32
+
33
+ if st.button("Ask Bot") and user_input:
34
+ # Send request to n8n webhook
35
+ payload = {
36
+ "question": user_input,
37
+ "data": df.to_dict(orient="records")
38
+ }
39
+
40
+ try:
41
+ response = requests.post(
42
+ "http://localhost:5678/webhook/chatbot",
43
+ json=payload,
44
+ timeout=60
45
+ )
46
+ if response.ok:
47
+ answer = response.json().get("answer", response.text)
48
+ st.success(answer)
49
+ else:
50
+ st.error(f"n8n Error: {response.status_code}")
51
+ except Exception as e:
52
+ st.error(f"Connection failed: {e}")
OLD/Main.py ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import numpy as np
3
+ from datetime import datetime, timedelta
4
+
5
+ def analyze_sales_data(data1, data2):
6
+ """
7
+ Analyze sales data to identify targets for mantri communication and village focus
8
+ """
9
+
10
+ # Convert date column if needed
11
+ data1['Date'] = pd.to_datetime(data1['Date'])
12
+
13
+ # Clean and preprocess data2
14
+ data2['Date'] = pd.to_datetime(data2['Date'])
15
+
16
+ # Calculate key metrics from Data1 (village level)
17
+ data1['Conversion_Rate'] = (data1['Contact_In_Group'] / data1['Sabhasad'] * 100).round(2)
18
+ data1['Conversion_Rate'] = data1['Conversion_Rate'].replace([np.inf, -np.inf], 0).fillna(0)
19
+ data1['Untapped_Potential'] = data1['Sabhasad'] - data1['Contact_In_Group']
20
+ data1['Sales_Per_Contact'] = (data1['Total_L'] / data1['Contact_In_Group']).round(2)
21
+ data1['Sales_Per_Contact'] = data1['Sales_Per_Contact'].replace([np.inf, -np.inf], 0).fillna(0)
22
+
23
+ # Calculate priority score for villages
24
+ data1['Priority_Score'] = (
25
+ (data1['Untapped_Potential'] / data1['Untapped_Potential'].max() * 50) +
26
+ ((100 - data1['Conversion_Rate']) / 100 * 50)
27
+ ).round(2)
28
+
29
+ # Analyze recent sales from Data2 (customer level)
30
+ # Since we don't have customer contact info, we'll analyze at village level
31
+ recent_sales = data2.groupby('Village').agg({
32
+ 'Total_L': ['sum', 'count'],
33
+ 'Date': 'max'
34
+ }).reset_index()
35
+
36
+ # Flatten the column names
37
+ recent_sales.columns = ['Village', 'Recent_Sales_L', 'Recent_Customers', 'Last_Sale_Date']
38
+
39
+ # Calculate days since last sale
40
+ recent_sales['Days_Since_Last_Sale'] = (datetime.now() - recent_sales['Last_Sale_Date']).dt.days
41
+
42
+ # Merge with Data1
43
+ analysis_df = data1.merge(recent_sales, on='Village', how='left')
44
+ analysis_df['Recent_Sales_L'] = analysis_df['Recent_Sales_L'].fillna(0)
45
+ analysis_df['Recent_Customers'] = analysis_df['Recent_Customers'].fillna(0)
46
+ analysis_df['Days_Since_Last_Sale'] = analysis_df['Days_Since_Last_Sale'].fillna(999)
47
+
48
+ # Generate recommendations for mantris
49
+ recommendations = []
50
+
51
+ for _, row in analysis_df.iterrows():
52
+ village = row['Village']
53
+ mantri = row['Mantri_Name']
54
+ mobile = row['Mantri_Mobile']
55
+ taluka = row['Taluka']
56
+ district = row['District']
57
+
58
+ # Recommendation logic
59
+ if row['Conversion_Rate'] < 20:
60
+ recommendations.append({
61
+ 'Village': village,
62
+ 'Taluka': taluka,
63
+ 'District': district,
64
+ 'Mantri': mantri,
65
+ 'Mobile': mobile,
66
+ 'Action': 'Send Marketing Team',
67
+ 'Reason': f'Low conversion rate ({row["Conversion_Rate"]:.1f}%) - Only {row["Contact_In_Group"]} of {row["Sabhasad"]} sabhasad contacted',
68
+ 'Priority': 'High',
69
+ 'Score': row['Priority_Score']
70
+ })
71
+ elif row['Untapped_Potential'] > 30:
72
+ recommendations.append({
73
+ 'Village': village,
74
+ 'Taluka': taluka,
75
+ 'District': district,
76
+ 'Mantri': mantri,
77
+ 'Mobile': mobile,
78
+ 'Action': 'Call Mantri for Follow-up',
79
+ 'Reason': f'High untapped potential ({row["Untapped_Potential"]} sabhasad not contacted)',
80
+ 'Priority': 'High',
81
+ 'Score': row['Priority_Score']
82
+ })
83
+ elif row['Days_Since_Last_Sale'] > 30:
84
+ recommendations.append({
85
+ 'Village': village,
86
+ 'Taluka': taluka,
87
+ 'District': district,
88
+ 'Mantri': mantri,
89
+ 'Mobile': mobile,
90
+ 'Action': 'Check on Mantri',
91
+ 'Reason': f'No recent sales ({row["Days_Since_Last_Sale"]} days since last sale)',
92
+ 'Priority': 'Medium',
93
+ 'Score': row['Priority_Score']
94
+ })
95
+ elif row['Sales_Per_Contact'] > 10:
96
+ recommendations.append({
97
+ 'Village': village,
98
+ 'Taluka': taluka,
99
+ 'District': district,
100
+ 'Mantri': mantri,
101
+ 'Mobile': mobile,
102
+ 'Action': 'Provide More Stock',
103
+ 'Reason': f'High sales per contact ({row["Sales_Per_Contact"]}L per contact)',
104
+ 'Priority': 'Medium',
105
+ 'Score': row['Priority_Score']
106
+ })
107
+ else:
108
+ recommendations.append({
109
+ 'Village': village,
110
+ 'Taluka': taluka,
111
+ 'District': district,
112
+ 'Mantri': mantri,
113
+ 'Mobile': mobile,
114
+ 'Action': 'Regular Follow-up',
115
+ 'Reason': 'Steady performance - maintain relationship',
116
+ 'Priority': 'Low',
117
+ 'Score': row['Priority_Score']
118
+ })
119
+
120
+ return pd.DataFrame(recommendations), analysis_df
121
+
122
+ def generate_mantri_messages(recommendations):
123
+ """
124
+ Generate personalized WhatsApp messages for mantris based on recommendations
125
+ """
126
+ messages = []
127
+
128
+ for _, row in recommendations.iterrows():
129
+ if row['Action'] == 'Send Marketing Team':
130
+ message = f"""
131
+ Namaste {row['Mantri']} Ji!
132
+
133
+ Aapke kshetra {row['Village']} mein humare calcium supplement ki conversion rate kam hai.
134
+ Humari marketing team aapke yaha demo dene aayegi.
135
+ Kripya taiyaari rakhein aur sabhi dudh utpadakon ko soochit karein.
136
+
137
+ Dhanyavaad,
138
+ Calcium Supplement Team
139
+ """
140
+ elif row['Action'] == 'Call Mantri for Follow-up':
141
+ message = f"""
142
+ Namaste {row['Mantri']} Ji!
143
+
144
+ Aapke kshetra {row['Village']} mein bahut se aise farmers hain jo abhi tak humare product se anabhijit hain.
145
+ Kripya unse sampark karein aur unhe product ke fayde batayein.
146
+ Aapke liye special commission offer hai agle 10 customers ke liye.
147
+
148
+ Dhanyavaad,
149
+ Calcium Supplement Team
150
+ """
151
+ elif row['Action'] == 'Check on Mantri':
152
+ message = f"""
153
+ Namaste {row['Mantri']} Ji!
154
+
155
+ Humne dekha ki aapke kshetra {row['Village']} mein kuch samay se sales nahi hue hain.
156
+ Kya koi samasya hai? Kya hum aapki kisi tarah madad kar sakte hain?
157
+
158
+ Kripya hame batayein.
159
+
160
+ Dhanyavaad,
161
+ Calcium Supplement Team
162
+ """
163
+ elif row['Action'] == 'Provide More Stock':
164
+ message = f"""
165
+ Namaste {row['Mantri']} Ji!
166
+
167
+ Badhai ho! Aapke kshetra {row['Village']} mein humare product ki demand badh rahi hai.
168
+ Kya aapko aur stock ki zaroorat hai? Hum jald se jald aapko extra stock bhej denge.
169
+
170
+ Dhanyavaad,
171
+ Calcium Supplement Team
172
+ """
173
+ else:
174
+ message = f"""
175
+ Namaste {row['Mantri']} Ji!
176
+
177
+ Aapke kshetra {row['Village']} mein humare product ki sales theek chal rahi hain.
178
+ Kripya aise hi continue rakhein aur koi bhi sujhav ho toh hame batayein.
179
+
180
+ Dhanyavaad,
181
+ Calcium Supplement Team
182
+ """
183
+
184
+ messages.append({
185
+ 'Mantri': row['Mantri'],
186
+ 'Mobile': row['Mobile'],
187
+ 'Village': row['Village'],
188
+ 'Action': row['Action'],
189
+ 'Message': message,
190
+ 'Priority': row['Priority']
191
+ })
192
+
193
+ return pd.DataFrame(messages)
194
+
195
+ def identify_demo_locations(analysis_df, top_n=5):
196
+ """
197
+ Identify the best locations for demos based on various factors
198
+ """
199
+ # Calculate a demo score based on multiple factors
200
+ analysis_df['Demo_Score'] = (
201
+ (analysis_df['Untapped_Potential'] / analysis_df['Untapped_Potential'].max() * 40) +
202
+ ((100 - analysis_df['Conversion_Rate']) / 100 * 30) +
203
+ (analysis_df['Recent_Sales_L'] / analysis_df['Recent_Sales_L'].max() * 30)
204
+ ).round(2)
205
+
206
+ # Get top locations for demos
207
+ demo_locations = analysis_df.nlargest(top_n, 'Demo_Score')[
208
+ ['Village', 'Taluka', 'District', 'Mantri_Name', 'Mantri_Mobile',
209
+ 'Conversion_Rate', 'Untapped_Potential', 'Demo_Score']
210
+ ]
211
+
212
+ return demo_locations
213
+
214
+ # Example usage with sample data structure
215
+ def main():
216
+ # Sample data based on your new structure
217
+ data2=pd.read_excel("sampletesting.xlsx",sheet_name="Sheet1")
218
+ data1=pd.read_excel("sampletesting.xlsx",sheet_name="Sheet2")
219
+
220
+ # Generate recommendations
221
+ recommendations, analysis = analyze_sales_data(data1, data2)
222
+
223
+ print("RECOMMENDED ACTIONS:")
224
+ print(recommendations.sort_values('Score', ascending=False).to_string(index=False))
225
+
226
+ # Generate messages for mantris
227
+ mantri_messages = generate_mantri_messages(recommendations)
228
+
229
+ print("\nMANTRI MESSAGES:")
230
+ for _, msg in mantri_messages.iterrows():
231
+ print(f"\nTo: {msg['Mantri']} ({msg['Mobile']}) - {msg['Village']}")
232
+ print(f"Action: {msg['Action']}")
233
+ print(f"Message: {msg['Message']}")
234
+
235
+ # Identify demo locations
236
+ demo_locations = identify_demo_locations(analysis)
237
+
238
+ print("\nTOP DEMO LOCATIONS:")
239
+ print(demo_locations.to_string(index=False))
240
+
241
+ return recommendations, mantri_messages, demo_locations
242
+
243
+ if __name__ == "__main__":
244
+ recommendations, mantri_messages, demo_locations = main()
OLD/__dbmlsystem.py ADDED
@@ -0,0 +1,596 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import numpy as np
4
+ import plotly.express as px
5
+ import plotly.graph_objects as go
6
+ from datetime import datetime, timedelta
7
+ from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
8
+ from sklearn.model_selection import train_test_split
9
+ from sklearn.preprocessing import StandardScaler
10
+ from sklearn.cluster import KMeans
11
+ import warnings
12
+ warnings.filterwarnings('ignore')
13
+
14
+ # Set page configuration
15
+ st.set_page_config(
16
+ page_title="Calcium Supplement Sales Automation",
17
+ page_icon="🐄",
18
+ layout="wide",
19
+ initial_sidebar_state="expanded"
20
+ )
21
+
22
+ # App title
23
+ st.title("🐄 Calcium Supplement Sales Automation Dashboard")
24
+ st.markdown("---")
25
+
26
+ # Your exact ML functions
27
+ def enhanced_analyze_sales_data(data1, data2):
28
+ """
29
+ Enhanced analysis with ML components for better predictions
30
+ """
31
+
32
+ data1['Date'] = pd.to_datetime(data1['Date'])
33
+ data2['Date'] = pd.to_datetime(data2['Date'])
34
+
35
+ # Calculate basic metrics
36
+ data1['Conversion_Rate'] = (data1['Contact_In_Group'] / data1['Sabhasad'] * 100).round(2)
37
+ data1['Conversion_Rate'] = data1['Conversion_Rate'].replace([np.inf, -np.inf], 0).fillna(0)
38
+ data1['Untapped_Potential'] = data1['Sabhasad'] - data1['Contact_In_Group']
39
+ data1['Sales_Per_Contact'] = (data1['Total_L'] / data1['Contact_In_Group']).round(2)
40
+ data1['Sales_Per_Contact'] = data1['Sales_Per_Contact'].replace([np.inf, -np.inf], 0).fillna(0)
41
+
42
+ # Analyze recent sales
43
+ recent_sales = data2.groupby('Village').agg({
44
+ 'Total_L': ['sum', 'count'],
45
+ 'Date': 'max'
46
+ }).reset_index()
47
+
48
+ recent_sales.columns = ['Village', 'Recent_Sales_L', 'Recent_Customers', 'Last_Sale_Date']
49
+ recent_sales['Days_Since_Last_Sale'] = (datetime.now() - recent_sales['Last_Sale_Date']).dt.days
50
+
51
+ # Merge data
52
+ analysis_df = data1.merge(recent_sales, on='Village', how='left')
53
+ analysis_df['Recent_Sales_L'] = analysis_df['Recent_Sales_L'].fillna(0)
54
+ analysis_df['Recent_Customers'] = analysis_df['Recent_Customers'].fillna(0)
55
+ analysis_df['Days_Since_Last_Sale'] = analysis_df['Days_Since_Last_Sale'].fillna(999)
56
+
57
+ # ML Component 1: Village Clustering for Segmentation
58
+ analysis_df = apply_village_clustering(analysis_df)
59
+
60
+ # ML Component 2: Predict Sales Potential
61
+ analysis_df = predict_sales_potential(analysis_df)
62
+
63
+ # ML Component 3: Action Recommendation Classifier
64
+ analysis_df = predict_recommended_actions(analysis_df)
65
+
66
+ # Generate recommendations based on ML predictions
67
+ recommendations = generate_ml_recommendations(analysis_df)
68
+
69
+ return recommendations, analysis_df
70
+
71
+ def apply_village_clustering(analysis_df):
72
+ """
73
+ Use K-Means clustering to segment villages into groups
74
+ """
75
+ # Prepare features for clustering
76
+ cluster_features = analysis_df[[
77
+ 'Conversion_Rate', 'Untapped_Potential', 'Sales_Per_Contact',
78
+ 'Recent_Sales_L', 'Days_Since_Last_Sale'
79
+ ]].fillna(0)
80
+
81
+ # Standardize features
82
+ scaler = StandardScaler()
83
+ scaled_features = scaler.fit_transform(cluster_features)
84
+
85
+ # Apply K-Means clustering
86
+ kmeans = KMeans(n_clusters=4, random_state=42, n_init=10)
87
+ clusters = kmeans.fit_predict(scaled_features)
88
+
89
+ # Add clusters to dataframe
90
+ analysis_df['Cluster'] = clusters
91
+
92
+ # Name the clusters based on characteristics
93
+ cluster_names = {
94
+ 0: 'High Potential - Low Engagement',
95
+ 1: 'Steady Performers',
96
+ 2: 'Underperforming',
97
+ 3: 'New/Developing'
98
+ }
99
+
100
+ analysis_df['Segment'] = analysis_df['Cluster'].map(cluster_names)
101
+
102
+ return analysis_df
103
+
104
+ def predict_sales_potential(analysis_df):
105
+ """
106
+ Predict sales potential for each village using Random Forest
107
+ """
108
+ # Prepare features for prediction
109
+ prediction_features = analysis_df[[
110
+ 'Sabhasad', 'Contact_In_Group', 'Conversion_Rate',
111
+ 'Untapped_Potential', 'Recent_Sales_L', 'Days_Since_Last_Sale'
112
+ ]].fillna(0)
113
+
114
+ # Target variable: Total_L (current sales)
115
+ target = analysis_df['Total_L'].fillna(0)
116
+
117
+ # Only train if we have enough data
118
+ if len(prediction_features) > 10:
119
+ # Split data
120
+ X_train, X_test, y_train, y_test = train_test_split(
121
+ prediction_features, target, test_size=0.2, random_state=42
122
+ )
123
+
124
+ # Train model
125
+ model = RandomForestRegressor(n_estimators=100, random_state=42)
126
+ model.fit(X_train, y_train)
127
+
128
+ # Make predictions
129
+ predictions = model.predict(prediction_features)
130
+
131
+ # Calculate feature importance
132
+ feature_importance = pd.DataFrame({
133
+ 'feature': prediction_features.columns,
134
+ 'importance': model.feature_importances_
135
+ }).sort_values('importance', ascending=False)
136
+
137
+ # Add predictions to dataframe
138
+ analysis_df['Predicted_Sales'] = predictions
139
+ analysis_df['Sales_Gap'] = analysis_df['Predicted_Sales'] - analysis_df['Total_L']
140
+ else:
141
+ # Fallback if not enough data
142
+ analysis_df['Predicted_Sales'] = analysis_df['Total_L']
143
+ analysis_df['Sales_Gap'] = 0
144
+
145
+ return analysis_df
146
+
147
+ def predict_recommended_actions(analysis_df):
148
+ """
149
+ Use ML to predict the best action for each village
150
+ """
151
+ # Define actions based on rules (for training data)
152
+ analysis_df['Action_Label'] = np.where(
153
+ analysis_df['Conversion_Rate'] < 20, 'Send Marketing Team',
154
+ np.where(
155
+ analysis_df['Untapped_Potential'] > 30, 'Call Mantri for Follow-up',
156
+ np.where(
157
+ analysis_df['Days_Since_Last_Sale'] > 30, 'Check on Mantri',
158
+ np.where(
159
+ analysis_df['Sales_Per_Contact'] > 10, 'Provide More Stock',
160
+ 'Regular Follow-up'
161
+ )
162
+ )
163
+ )
164
+ )
165
+
166
+ # Prepare features for classification
167
+ classification_features = analysis_df[[
168
+ 'Conversion_Rate', 'Untapped_Potential', 'Sales_Per_Contact',
169
+ 'Recent_Sales_L', 'Days_Since_Last_Sale', 'Sales_Gap'
170
+ ]].fillna(0)
171
+
172
+ # Target variable: Action_Label
173
+ target = analysis_df['Action_Label']
174
+
175
+ # Only train if we have enough data
176
+ if len(classification_features) > 10 and len(target.unique()) > 1:
177
+ # Split data
178
+ X_train, X_test, y_train, y_test = train_test_split(
179
+ classification_features, target, test_size=0.2, random_state=42, stratify=target
180
+ )
181
+
182
+ # Train classifier
183
+ clf = RandomForestClassifier(n_estimators=100, random_state=42)
184
+ clf.fit(X_train, y_train)
185
+
186
+ # Make predictions
187
+ predictions = clf.predict(classification_features)
188
+ prediction_proba = clf.predict_proba(classification_features)
189
+
190
+ # Add predictions to dataframe
191
+ analysis_df['ML_Recommended_Action'] = predictions
192
+ analysis_df['Action_Confidence'] = np.max(prediction_proba, axis=1)
193
+ else:
194
+ # Fallback to rule-based if not enough data
195
+ analysis_df['ML_Recommended_Action'] = analysis_df['Action_Label']
196
+ analysis_df['Action_Confidence'] = 1.0
197
+
198
+ return analysis_df
199
+
200
+ def generate_ml_recommendations(analysis_df):
201
+ """
202
+ Generate recommendations based on ML predictions
203
+ """
204
+ recommendations = []
205
+
206
+ for _, row in analysis_df.iterrows():
207
+ village = row['Village']
208
+ mantri = row['Mantri_Name']
209
+ mobile = row['Mantri_Mobile']
210
+ taluka = row['Taluka']
211
+ district = row['District']
212
+ segment = row['Segment']
213
+ action = row['ML_Recommended_Action']
214
+ confidence = row['Action_Confidence']
215
+
216
+ # Generate reason based on ML prediction
217
+ if action == 'Send Marketing Team':
218
+ reason = f"ML predicts marketing team needed (Confidence: {confidence:.2f}). Segment: {segment}"
219
+ priority = 'High'
220
+ elif action == 'Call Mantri for Follow-up':
221
+ reason = f"ML predicts mantri follow-up needed (Confidence: {confidence:.2f}). Segment: {segment}"
222
+ priority = 'High'
223
+ elif action == 'Check on Mantri':
224
+ reason = f"ML suggests checking on mantri (Confidence: {confidence:.2f}). Segment: {segment}"
225
+ priority = 'Medium'
226
+ elif action == 'Provide More Stock':
227
+ reason = f"ML predicts stock increase needed (Confidence: {confidence:.2f}). Segment: {segment}"
228
+ priority = 'Medium'
229
+ else:
230
+ reason = f"ML recommends regular follow-up (Confidence: {confidence:.2f}). Segment: {segment}"
231
+ priority = 'Low'
232
+
233
+ recommendations.append({
234
+ 'Village': village,
235
+ 'Taluka': taluka,
236
+ 'District': district,
237
+ 'Mantri': mantri,
238
+ 'Mobile': mobile,
239
+ 'Action': action,
240
+ 'Reason': reason,
241
+ 'Priority': priority,
242
+ 'Confidence': confidence,
243
+ 'Segment': segment,
244
+ 'Sales_Gap': row.get('Sales_Gap', 0)
245
+ })
246
+
247
+ return pd.DataFrame(recommendations)
248
+
249
+ def generate_ml_mantri_messages(recommendations):
250
+ """
251
+ Generate personalized messages based on ML recommendations
252
+ """
253
+ messages = []
254
+
255
+ for _, row in recommendations.iterrows():
256
+ if row['Action'] == 'Send Marketing Team':
257
+ message = f"""
258
+ Namaste {row['Mantri']} Ji!
259
+
260
+ Our AI system has identified that your village {row['Village']} has high potential for growth.
261
+ We're sending our marketing team to conduct demo sessions and help you reach more customers.
262
+
263
+ Based on our analysis:
264
+ - Segment: {row['Segment']}
265
+ - Confidence: {row['Confidence']*100:.1f}%
266
+
267
+ Please prepare for their visit and notify potential customers.
268
+
269
+ Dhanyavaad,
270
+ Calcium Supplement Team
271
+ """
272
+ elif row['Action'] == 'Call Mantri for Follow-up':
273
+ message = f"""
274
+ Namaste {row['Mantri']} Ji!
275
+
276
+ Our AI analysis shows significant untapped potential in {row['Village']}.
277
+ We recommend focusing on follow-up with these customers:
278
+
279
+ - Segment: {row['Segment']}
280
+ - Confidence: {row['Confidence']*100:.1f}%
281
+
282
+ A special commission offer is available for your next 10 customers.
283
+
284
+ Dhanyavaad,
285
+ Calcium Supplement Team
286
+ """
287
+ elif row['Action'] == 'Check on Mantri':
288
+ message = f"""
289
+ Namaste {row['Mantri']} Ji!
290
+
291
+ Our system shows reduced activity in {row['Village']}.
292
+ Is everything alright? Do you need any support from our team?
293
+
294
+ - Segment: {row['Segment']}
295
+ - Confidence: {row['Confidence']*100:.1f}%
296
+
297
+ Please let us know how we can help.
298
+
299
+ Dhanyavaad,
300
+ Calcium Supplement Team
301
+ """
302
+ elif row['Action'] == 'Provide More Stock':
303
+ message = f"""
304
+ Namaste {row['Mantri']} Ji!
305
+
306
+ Great news! Our AI predicts increased demand in {row['Village']}.
307
+ Would you like us to send additional stock?
308
+
309
+ - Segment: {row['Segment']}
310
+ - Confidence: {row['Confidence']*100:.1f}%
311
+ - Predicted Sales Gap: {row['Sales_Gap']:.1f}L
312
+
313
+ Please confirm your additional requirements.
314
+
315
+ Dhanyavaad,
316
+ Calcium Supplement Team
317
+ """
318
+ else:
319
+ message = f"""
320
+ Namaste {row['Mantri']} Ji!
321
+
322
+ Our system shows steady performance in {row['Village']}.
323
+ Keep up the good work!
324
+
325
+ - Segment: {row['Segment']}
326
+ - Confidence: {row['Confidence']*100:.1f}%
327
+
328
+ As always, let us know if you need any support.
329
+
330
+ Dhanyavaad,
331
+ Calcium Supplement Team
332
+ """
333
+
334
+ messages.append({
335
+ 'Mantri': row['Mantri'],
336
+ 'Mobile': row['Mobile'],
337
+ 'Village': row['Village'],
338
+ 'Action': row['Action'],
339
+ 'Message': message,
340
+ 'Priority': row['Priority'],
341
+ 'Confidence': row['Confidence']
342
+ })
343
+
344
+ return pd.DataFrame(messages)
345
+
346
+ # Visualization functions
347
+ def plot_village_performance(analysis_df):
348
+ """Create performance visualization for villages"""
349
+ fig = px.scatter(analysis_df,
350
+ x='Conversion_Rate',
351
+ y='Untapped_Potential',
352
+ size='Total_L',
353
+ color='Segment',
354
+ hover_name='Village',
355
+ title='Village Performance Analysis',
356
+ labels={'Conversion_Rate': 'Conversion Rate (%)',
357
+ 'Untapped_Potential': 'Untapped Potential'})
358
+
359
+ fig.update_layout(height=500)
360
+ return fig
361
+
362
+ def plot_sales_trends(analysis_df):
363
+ """Create sales trends visualization"""
364
+ fig = px.bar(analysis_df,
365
+ x='Village',
366
+ y='Total_L',
367
+ color='Segment',
368
+ title='Total Sales by Village',
369
+ labels={'Total_L': 'Total Sales (L)', 'Village': 'Village'})
370
+
371
+ fig.update_layout(height=400, xaxis_tickangle=-45)
372
+ return fig
373
+
374
+ def plot_priority_matrix(recommendations):
375
+ """Create priority matrix visualization"""
376
+ priority_order = {'High': 3, 'Medium': 2, 'Low': 1}
377
+ recommendations['Priority_Value'] = recommendations['Priority'].map(priority_order)
378
+
379
+ fig = px.treemap(recommendations,
380
+ path=['Priority', 'Village'],
381
+ values='Priority_Value',
382
+ color='Priority_Value',
383
+ color_continuous_scale='RdYlGn_r',
384
+ title='Action Priority Matrix')
385
+
386
+ fig.update_layout(height=500)
387
+ return fig
388
+
389
+ def display_key_metrics(analysis_df):
390
+ """Display key performance metrics"""
391
+ col1, col2, col3, col4 = st.columns(4)
392
+
393
+ with col1:
394
+ st.metric("Total Villages", len(analysis_df))
395
+ with col2:
396
+ avg_conversion = analysis_df['Conversion_Rate'].mean()
397
+ st.metric("Avg Conversion Rate", f"{avg_conversion:.1f}%")
398
+ with col3:
399
+ total_untapped = analysis_df['Untapped_Potential'].sum()
400
+ st.metric("Total Untapped Potential", f"{total_untapped}")
401
+ with col4:
402
+ total_sales = analysis_df['Total_L'].sum()
403
+ st.metric("Total Sales (L)", f"{total_sales}")
404
+
405
+ # Initialize session state
406
+ if 'data1' not in st.session_state:
407
+ st.session_state.data1 = None
408
+ if 'data2' not in st.session_state:
409
+ st.session_state.data2 = None
410
+ if 'analysis_df' not in st.session_state:
411
+ st.session_state.analysis_df = None
412
+ if 'recommendations' not in st.session_state:
413
+ st.session_state.recommendations = None
414
+ if 'ml_messages' not in st.session_state:
415
+ st.session_state.ml_messages = None
416
+
417
+ # Sidebar
418
+ with st.sidebar:
419
+ st.header("Data Input")
420
+
421
+ # File uploaders
422
+ st.subheader("Upload Village Data (Data1)")
423
+ uploaded_data1 = st.file_uploader("CSV or Excel file", type=["csv", "xlsx"], key="data1")
424
+
425
+ st.subheader("Upload Sales Data (Data2)")
426
+ uploaded_data2 = st.file_uploader("CSV or Excel file", type=["csv", "xlsx"], key="data2")
427
+
428
+ if st.button("Load Data and Run ML Analysis"):
429
+ if uploaded_data1 and uploaded_data2:
430
+ try:
431
+ # Load data
432
+ if uploaded_data1.name.endswith('.csv'):
433
+ data1 = pd.read_csv(uploaded_data1)
434
+ else:
435
+ data1 = pd.read_excel(uploaded_data1)
436
+
437
+ if uploaded_data2.name.endswith('.csv'):
438
+ data2 = pd.read_csv(uploaded_data2)
439
+ else:
440
+ data2 = pd.read_excel(uploaded_data2)
441
+
442
+ # Store in session state
443
+ st.session_state.data1 = data1
444
+ st.session_state.data2 = data2
445
+
446
+ # Run ML analysis
447
+ with st.spinner("Running ML analysis..."):
448
+ recommendations, analysis_df = enhanced_analyze_sales_data(data1, data2)
449
+ st.session_state.analysis_df = analysis_df
450
+ st.session_state.recommendations = recommendations
451
+
452
+ ml_messages = generate_ml_mantri_messages(recommendations)
453
+ st.session_state.ml_messages = ml_messages
454
+
455
+ st.success("ML analysis completed successfully!")
456
+
457
+ except Exception as e:
458
+ st.error(f"Error processing data: {str(e)}")
459
+ else:
460
+ st.error("Please upload both files to proceed")
461
+
462
+ # Main content
463
+ if st.session_state.analysis_df is not None and st.session_state.recommendations is not None:
464
+ # Display dashboard
465
+ tab1, tab2, tab3, tab4 = st.tabs(["Dashboard", "Village Analysis", "Actions & Messages", "Team Dispatch"])
466
+
467
+ with tab1:
468
+ st.header("ML-Powered Performance Dashboard")
469
+ display_key_metrics(st.session_state.analysis_df)
470
+
471
+ col1, col2 = st.columns(2)
472
+
473
+ with col1:
474
+ st.plotly_chart(plot_village_performance(st.session_state.analysis_df), use_container_width=True)
475
+
476
+ with col2:
477
+ st.plotly_chart(plot_priority_matrix(st.session_state.recommendations), use_container_width=True)
478
+
479
+ st.plotly_chart(plot_sales_trends(st.session_state.analysis_df), use_container_width=True)
480
+
481
+ with tab2:
482
+ st.header("Village Analysis with ML Segmentation")
483
+
484
+ selected_village = st.selectbox("Select Village", st.session_state.analysis_df['Village'].unique())
485
+ village_data = st.session_state.analysis_df[st.session_state.analysis_df['Village'] == selected_village].iloc[0]
486
+
487
+ col1, col2 = st.columns(2)
488
+
489
+ with col1:
490
+ st.subheader("Village Details")
491
+ st.write(f"**Village:** {village_data['Village']}")
492
+ st.write(f"**Taluka:** {village_data['Taluka']}")
493
+ st.write(f"**District:** {village_data['District']}")
494
+ st.write(f"**Mantri:** {village_data['Mantri_Name']}")
495
+ st.write(f"**Mantri Mobile:** {village_data['Mantri_Mobile']}")
496
+ st.write(f"**Segment:** {village_data.get('Segment', 'N/A')}")
497
+ st.write(f"**ML Recommended Action:** {village_data.get('ML_Recommended_Action', 'N/A')}")
498
+ st.write(f"**Action Confidence:** {village_data.get('Action_Confidence', 'N/A'):.2f}")
499
+
500
+ with col2:
501
+ st.subheader("Performance Metrics")
502
+ st.write(f"**Sabhasad:** {village_data['Sabhasad']}")
503
+ st.write(f"**Contacted:** {village_data['Contact_In_Group']}")
504
+ st.write(f"**Conversion Rate:** {village_data['Conversion_Rate']}%")
505
+ st.write(f"**Untapped Potential:** {village_data['Untapped_Potential']}")
506
+ st.write(f"**Total Sales:** {village_data['Total_L']}L")
507
+ st.write(f"**Sales per Contact:** {village_data['Sales_Per_Contact']}L")
508
+ st.write(f"**Predicted Sales:** {village_data.get('Predicted_Sales', 'N/A'):.1f}L")
509
+ st.write(f"**Sales Gap:** {village_data.get('Sales_Gap', 'N/A'):.1f}L")
510
+
511
+ with tab3:
512
+ st.header("ML-Based Actions & Messages")
513
+
514
+ st.subheader("ML-Generated Recommendations")
515
+ st.dataframe(st.session_state.recommendations)
516
+
517
+ # Download recommendations
518
+ csv_data = st.session_state.recommendations.to_csv(index=False)
519
+ st.download_button(
520
+ label="Download Recommendations as CSV",
521
+ data=csv_data,
522
+ file_name="ml_sales_recommendations.csv",
523
+ mime="text/csv"
524
+ )
525
+
526
+ st.subheader("Generate ML-Powered Messages")
527
+ selected_mantri = st.selectbox("Select Mantri", st.session_state.recommendations['Mantri'].unique())
528
+ mantri_data = st.session_state.recommendations[
529
+ st.session_state.recommendations['Mantri'] == selected_mantri].iloc[0]
530
+
531
+ message_df = st.session_state.ml_messages[
532
+ st.session_state.ml_messages['Mantri'] == selected_mantri]
533
+
534
+ if not message_df.empty:
535
+ message = message_df.iloc[0]['Message']
536
+ st.text_area("ML-Generated Message", message, height=300)
537
+
538
+ if st.button("Send Message"):
539
+ st.success(f"Message sent to {mantri_data['Mantri']} at {mantri_data['Mobile']}")
540
+
541
+ st.subheader("Bulk Message Sender")
542
+ if st.button("Generate All ML Messages"):
543
+ st.session_state.all_messages = st.session_state.ml_messages
544
+
545
+ if 'all_messages' in st.session_state:
546
+ st.dataframe(st.session_state.all_messages[['Mantri', 'Village', 'Action', 'Priority', 'Confidence']])
547
+
548
+ if st.button("Send All ML Messages"):
549
+ progress_bar = st.progress(0)
550
+ for i, row in st.session_state.all_messages.iterrows():
551
+ # Simulate sending message
552
+ progress_bar.progress((i + 1) / len(st.session_state.all_messages))
553
+ st.success("All ML-powered messages sent successfully!")
554
+
555
+ with tab4:
556
+ st.header("Marketing Team Dispatch with ML Insights")
557
+
558
+ st.subheader("Villages Needing Team Visit (ML Identified)")
559
+ high_priority = st.session_state.recommendations[
560
+ st.session_state.recommendations['Action'] == 'Send Marketing Team']
561
+
562
+ if not high_priority.empty:
563
+ for _, row in high_priority.iterrows():
564
+ with st.expander(f"{row['Village']} - {row['Mantri']} (Confidence: {row['Confidence']:.2f})"):
565
+ st.write(f"**Reason:** {row['Reason']}")
566
+ st.write(f"**Segment:** {row['Segment']}")
567
+ st.write(f"**Sales Gap:** {row['Sales_Gap']:.1f}L")
568
+
569
+ dispatch_date = st.date_input("Dispatch Date", key=f"date_{row['Village']}")
570
+ team_size = st.slider("Team Size", 1, 5, 2, key=f"size_{row['Village']}")
571
+
572
+ if st.button("Schedule Dispatch", key=f"dispatch_{row['Village']}"):
573
+ st.success(f"Team dispatch scheduled for {row['Village']} on {dispatch_date}")
574
+ else:
575
+ st.info("No villages currently require immediate team dispatch based on ML analysis.")
576
+
577
+ st.subheader("ML Performance Insights")
578
+ st.write("Based on our machine learning analysis, here are key insights:")
579
+
580
+ # Show segment distribution
581
+ segment_counts = st.session_state.analysis_df['Segment'].value_counts()
582
+ fig = px.pie(values=segment_counts.values, names=segment_counts.index,
583
+ title="Village Segment Distribution")
584
+ st.plotly_chart(fig, use_container_width=True)
585
+
586
+ # Show confidence distribution
587
+ fig = px.histogram(st.session_state.recommendations, x='Confidence',
588
+ title='Confidence Distribution of ML Recommendations')
589
+ st.plotly_chart(fig, use_container_width=True)
590
+
591
+ else:
592
+ st.info("Please upload your data files using the sidebar and click 'Load Data and Run ML Analysis' to get started.")
593
+
594
+ # Footer
595
+ st.markdown("---")
596
+ st.markdown("**ML-Powered Calcium Supplement Sales Automation System** | For internal use only")
OLD/sampleDashboard.py ADDED
@@ -0,0 +1,350 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import numpy as np
4
+ import plotly.express as px
5
+ import plotly.graph_objects as go
6
+ from datetime import datetime, timedelta
7
+ import time
8
+
9
+ # Set page configuration
10
+ st.set_page_config(
11
+ page_title="Calcium Supplement Sales Dashboard",
12
+ page_icon="🐄",
13
+ layout="wide",
14
+ initial_sidebar_state="expanded"
15
+ )
16
+
17
+ # Sample data (replace with your actual data loading)
18
+ @st.cache_data
19
+ def load_data():
20
+ # Sales data with customer information
21
+ sales_data = pd.DataFrame({
22
+ 'Date': ['2025-06-01', '2025-06-01', '2025-06-10', '2025-06-11', '2025-06-12',
23
+ '2025-07-30', '2025-07-30', '2025-07-31', '2025-07-31', '2025-07-31'],
24
+ 'Customer': ['Gopalbhai', 'Ramprasad Khatik', 'Vikramsinh', 'Prahladbhai -Mantry', 'V S Stud Farm',
25
+ 'Hemendrabhai Parmar', 'Sundarbhai', 'Kamleshbhai Vasava -Mantry', 'Kiranbhai -Mantry', 'Kiritbhai'],
26
+ 'Village': ['Shilly', 'Rajasthan', 'Mithapura', 'Bhalod Dairy', 'Waghodia',
27
+ 'Panchdevla', 'Siyali', 'Moran', 'Talodara', 'Sindhrot'],
28
+ 'Total_L': [35.0, 400.0, 30.0, 7.0, 400.0, 50.0, 13.0, 1.0, 1.0, 30.0]
29
+ })
30
+
31
+ # Mantri data with village information
32
+ mantri_data = pd.DataFrame({
33
+ 'DATE': ['2024-03-08', '2025-06-03', '2025-02-23', '2025-05-28', '2025-05-02',
34
+ '2024-09-21', '2024-10-26', '2024-03-19', '2025-01-30', '2025-07-18'],
35
+ 'VILLAGE': ['JILOD', 'MANJIPURA', 'GOTHADA', 'UNTKHARI', 'VEMAR',
36
+ 'KANODA', 'KOTAMBI', 'RASNOL', 'JITPURA', 'BHATPURA'],
37
+ 'MANTRY_NAME': ['AJAYBHAI PATEL', 'AJAYBHAI PATEL', 'AJGAR KHAN', 'AMBALAL CHAUHAN', 'AMBALAL GOHIL',
38
+ 'VINUBHAI SOLANKI', 'VISHNUBHAI', 'VITHTHALBHAI', 'YOGESHBHAI', 'YUVRAJSINH'],
39
+ 'MOBILE_NO': [7984136988, 9737910554, 9724831903, 9313860902, 9978081739,
40
+ 9998756469, 9909550170, 9924590017, 7990383811, 6353209447],
41
+ 'sabhasad': [38, 21, 3, 0, 2, 0, 14, 1183, 8, 6],
42
+ 'contact_in_group': [38.0, 16.0, 2.0, 0.0, 0.0, 0.0, 14.0, 268.0, 5.0, 4.0],
43
+ 'TOTAL_L': [99.0, 120.0, 19.0, 87.0, 32.0, 60.0, 54.0, 82.0, 25.0, 11.0]
44
+ })
45
+
46
+ # Convert dates to datetime
47
+ sales_data['Date'] = pd.to_datetime(sales_data['Date'])
48
+ mantri_data['DATE'] = pd.to_datetime(mantri_data['DATE'], errors='coerce')
49
+
50
+ return sales_data, mantri_data
51
+
52
+ # Analysis functions
53
+ def analyze_mantri_performance(mantri_data, sales_data):
54
+ mantri_data = mantri_data.copy()
55
+
56
+ # Calculate performance metrics
57
+ mantri_data['Conversion_Rate'] = (mantri_data['contact_in_group'] / mantri_data['sabhasad'] * 100).round(2)
58
+ mantri_data['Conversion_Rate'] = mantri_data['Conversion_Rate'].replace([np.inf, -np.inf], 0).fillna(0)
59
+ mantri_data['Untapped_Potential'] = mantri_data['sabhasad'] - mantri_data['contact_in_group']
60
+ mantri_data['Sales_Efficiency'] = (mantri_data['TOTAL_L'] / mantri_data['contact_in_group']).round(2)
61
+ mantri_data['Sales_Efficiency'] = mantri_data['Sales_Efficiency'].replace([np.inf, -np.inf], 0).fillna(0)
62
+
63
+ # Priority score calculation
64
+ mantri_data['Priority_Score'] = (
65
+ (mantri_data['Untapped_Potential'] / mantri_data['Untapped_Potential'].max() * 50) +
66
+ ((100 - mantri_data['Conversion_Rate']) / 100 * 50)
67
+ ).round(2)
68
+
69
+ # Add recent sales data
70
+ recent_sales = sales_data.groupby('Village').agg({
71
+ 'Total_L': 'sum',
72
+ 'Customer': 'count'
73
+ }).reset_index()
74
+ recent_sales.columns = ['VILLAGE', 'Recent_Sales', 'Recent_Customers']
75
+
76
+ mantri_data = mantri_data.merge(recent_sales, on='VILLAGE', how='left')
77
+ mantri_data['Recent_Sales'] = mantri_data['Recent_Sales'].fillna(0)
78
+ mantri_data['Recent_Customers'] = mantri_data['Recent_Customers'].fillna(0)
79
+
80
+ return mantri_data
81
+
82
+ def analyze_village_performance(sales_data, mantri_data):
83
+ # Group sales by village
84
+ village_sales = sales_data.groupby('Village').agg({
85
+ 'Total_L': 'sum',
86
+ 'Customer': 'count',
87
+ 'Date': 'max'
88
+ }).reset_index()
89
+ village_sales.columns = ['Village', 'Total_Sales', 'Customer_Count', 'Last_Sale_Date']
90
+
91
+ # Calculate days since last sale
92
+ village_sales['Days_Since_Last_Sale'] = (datetime.now() - village_sales['Last_Sale_Date']).dt.days
93
+
94
+ # Merge with mantri data
95
+ mantri_summary = mantri_data[['VILLAGE', 'MANTRY_NAME', 'MOBILE_NO', 'sabhasad', 'contact_in_group']]
96
+ mantri_summary.columns = ['Village', 'Mantri_Name', 'Mantri_Mobile', 'Sabhasad', 'Contacts']
97
+
98
+ village_performance = village_sales.merge(mantri_summary, on='Village', how='left')
99
+
100
+ # Calculate performance metrics
101
+ village_performance['Conversion_Rate'] = (village_performance['Contacts'] / village_performance['Sabhasad'] * 100).round(2)
102
+ village_performance['Conversion_Rate'] = village_performance['Conversion_Rate'].replace([np.inf, -np.inf], 0).fillna(0)
103
+ village_performance['Untapped_Potential'] = village_performance['Sabhasad'] - village_performance['Contacts']
104
+
105
+ return village_performance
106
+
107
+ # Message templates
108
+ def get_mantri_message_template(mantri_name, village, reason, performance_data):
109
+ templates = {
110
+ 'Low Conversion': f"""
111
+ Namaste {mantri_name} Ji!
112
+
113
+ Aapke kshetra {village} mein humare calcium supplement ki conversion rate kam hai ({performance_data['Conversion_Rate']}%).
114
+ Humari marketing team aapke yaha demo dene aayegi.
115
+ Kripya taiyaari rakhein aur sabhi dudh utpadakon ko soochit karein.
116
+
117
+ Aapke paas abhi bhi {int(performance_data['Untapped_Potential'])} aise farmers hain jo product nahi use kar rahe hain.
118
+
119
+ Dhanyavaad,
120
+ Calcium Supplement Team
121
+ """,
122
+ 'High Potential': f"""
123
+ Namaste {mantri_name} Ji!
124
+
125
+ Aapke kshetra {village} mein {int(performance_data['Untapped_Potential'])} aise farmers hain jo abhi tak humare product se anabhijit hain.
126
+ Kripya unse sampark karein aur unhe product ke fayde batayein.
127
+ Aapke liye special commission offer hai agle 10 naye customers ke liye.
128
+
129
+ Dhanyavaad,
130
+ Calcium Supplement Team
131
+ """,
132
+ 'Good Performance': f"""
133
+ Namaste {mantri_name} Ji!
134
+
135
+ Aapke kshetra {village} mein humare product ki demand badh rahi hai.
136
+ Aapki conversion rate {performance_data['Conversion_Rate']}% hai jo bahut achchi hai.
137
+
138
+ Kripya farmers ko yaad dilaein ki pregnancy ke 3-9 mahine aur delivery ke baad calcium supplement zaroori hai.
139
+
140
+ Dhanyavaad,
141
+ Calcium Supplement Team
142
+ """
143
+ }
144
+
145
+ return templates.get(reason, "Custom message based on analysis")
146
+
147
+ # Load data
148
+ sales_data, mantri_data = load_data()
149
+ mantri_performance = analyze_mantri_performance(mantri_data, sales_data)
150
+ village_performance = analyze_village_performance(sales_data, mantri_data)
151
+
152
+ # Streamlit app
153
+ st.title("🐄 Calcium Supplement Sales Automation Dashboard")
154
+ st.markdown("---")
155
+
156
+ # Sidebar
157
+ st.sidebar.header("Navigation")
158
+ section = st.sidebar.radio("Go to", ["Dashboard", "Mantri Performance", "Village Analysis", "Message Center", "Team Dispatch"])
159
+
160
+ # Dashboard
161
+ if section == "Dashboard":
162
+ st.header("Sales Performance Overview")
163
+
164
+ col1, col2, col3, col4 = st.columns(4)
165
+
166
+ with col1:
167
+ st.metric("Total Villages Covered", len(mantri_performance))
168
+ with col2:
169
+ st.metric("Total Mantris", len(mantri_performance['MANTRY_NAME'].unique()))
170
+ with col3:
171
+ st.metric("Total Sales (Liters)", mantri_performance['TOTAL_L'].sum())
172
+ with col4:
173
+ avg_conversion = mantri_performance['Conversion_Rate'].mean()
174
+ st.metric("Avg Conversion Rate", f"{avg_conversion:.2f}%")
175
+
176
+ st.subheader("Top Priority Mantris")
177
+ priority_mantris = mantri_performance.nlargest(5, 'Priority_Score')[['MANTRY_NAME', 'VILLAGE', 'Conversion_Rate', 'Untapped_Potential', 'Priority_Score']]
178
+ st.dataframe(priority_mantris)
179
+
180
+ st.subheader("Sales Distribution by Village")
181
+ fig = px.bar(mantri_performance, x='VILLAGE', y='TOTAL_L', title='Total Sales by Village')
182
+ st.plotly_chart(fig, use_container_width=True)
183
+
184
+ st.subheader("Conversion Rate vs Untapped Potential")
185
+ fig = px.scatter(mantri_performance, x='Conversion_Rate', y='Untapped_Potential',
186
+ size='TOTAL_L', color='VILLAGE', hover_name='MANTRY_NAME',
187
+ title='Mantri Performance Analysis')
188
+ st.plotly_chart(fig, use_container_width=True)
189
+
190
+ # Mantri Performance
191
+ elif section == "Mantri Performance":
192
+ st.header("Mantri Performance Analysis")
193
+
194
+ selected_mantri = st.selectbox("Select Mantri", mantri_performance['MANTRY_NAME'].unique())
195
+ mantri_data = mantri_performance[mantri_performance['MANTRY_NAME'] == selected_mantri].iloc[0]
196
+
197
+ col1, col2, col3, col4 = st.columns(4)
198
+
199
+ with col1:
200
+ st.metric("Mantri", mantri_data['MANTRY_NAME'])
201
+ with col2:
202
+ st.metric("Village", mantri_data['VILLAGE'])
203
+ with col3:
204
+ st.metric("Conversion Rate", f"{mantri_data['Conversion_Rate']}%")
205
+ with col4:
206
+ st.metric("Untapped Potential", int(mantri_data['Untapped_Potential']))
207
+
208
+ st.subheader("Mantri Details")
209
+ st.dataframe(mantri_data)
210
+
211
+ st.subheader("Action Recommendations")
212
+ if mantri_data['Conversion_Rate'] < 20:
213
+ st.error(f"**Send Marketing Team**: Conversion rate is low ({mantri_data['Conversion_Rate']}%). Need demos and awareness campaigns.")
214
+ if mantri_data['Untapped_Potential'] > 10:
215
+ st.warning(f"**Call Mantri**: {int(mantri_data['Untapped_Potential'])} farmers still not converted. Push Mantri to contact them.")
216
+ if mantri_data['Conversion_Rate'] > 50:
217
+ st.success(f"**Expand Success**: This mantri is performing well. Consider replicating their strategies.")
218
+
219
+ # Village Analysis
220
+ elif section == "Village Analysis":
221
+ st.header("Village Performance Analysis")
222
+
223
+ selected_village = st.selectbox("Select Village", village_performance['Village'].unique())
224
+ village_data = village_performance[village_performance['Village'] == selected_village].iloc[0]
225
+
226
+ col1, col2, col3, col4 = st.columns(4)
227
+
228
+ with col1:
229
+ st.metric("Village", village_data['Village'])
230
+ with col2:
231
+ st.metric("Mantri", village_data['Mantri_Name'])
232
+ with col3:
233
+ st.metric("Total Sales (L)", village_data['Total_Sales'])
234
+ with col4:
235
+ st.metric("Days Since Last Sale", village_data['Days_Since_Last_Sale'])
236
+
237
+ st.subheader("Village Details")
238
+ st.dataframe(village_data)
239
+
240
+ st.subheader("Action Recommendations")
241
+ if village_data['Days_Since_Last_Sale'] > 30:
242
+ st.error(f"**Send Marketing Team**: No sales in {village_data['Days_Since_Last_Sale']} days. Need immediate attention.")
243
+ if village_data['Conversion_Rate'] < 25:
244
+ st.warning(f"**Low Conversion**: Only {village_data['Conversion_Rate']}% of potential customers are converted.")
245
+ if village_data['Total_Sales'] > 100:
246
+ st.success(f"**High Performer**: This village has high sales volume. Consider expanding product range.")
247
+
248
+ # Message Center
249
+ elif section == "Message Center":
250
+ st.header("Message Center")
251
+
252
+ st.subheader("Mantri Communication")
253
+ selected_mantri = st.selectbox("Select Mantri", mantri_performance['MANTRY_NAME'].unique())
254
+ mantri_data = mantri_performance[mantri_performance['MANTRY_NAME'] == selected_mantri].iloc[0]
255
+
256
+ st.write(f"**Village:** {mantri_data['VILLAGE']}")
257
+ st.write(f"**Conversion Rate:** {mantri_data['Conversion_Rate']}%")
258
+ st.write(f"**Untapped Potential:** {int(mantri_data['Untapped_Potential'])} farmers")
259
+
260
+ if mantri_data['Conversion_Rate'] < 20:
261
+ reason = "Low Conversion"
262
+ elif mantri_data['Untapped_Potential'] > 10:
263
+ reason = "High Potential"
264
+ else:
265
+ reason = "Good Performance"
266
+
267
+ message = get_mantri_message_template(
268
+ mantri_data['MANTRY_NAME'],
269
+ mantri_data['VILLAGE'],
270
+ reason,
271
+ mantri_data
272
+ )
273
+
274
+ st.text_area("Generated Message", message, height=200)
275
+
276
+ if st.button("Send to Mantri"):
277
+ st.success(f"Message sent to {mantri_data['MANTRY_NAME']} at {mantri_data['MOBILE_NO']}")
278
+ # Here you would integrate with WhatsApp API
279
+
280
+ st.subheader("Bulk Message Sender")
281
+ st.write("Send messages to multiple mantris at once")
282
+
283
+ options = st.multiselect("Select Mantris", mantri_performance['MANTRY_NAME'].unique())
284
+ message_template = st.text_area("Message Template", height=100)
285
+
286
+ if st.button("Send to Selected Mantris"):
287
+ progress_bar = st.progress(0)
288
+ for i, mantri in enumerate(options):
289
+ # Simulate sending
290
+ time.sleep(0.5)
291
+ progress_bar.progress((i + 1) / len(options))
292
+ st.success(f"Messages sent to {len(options)} mantris")
293
+
294
+ # Team Dispatch
295
+ elif section == "Team Dispatch":
296
+ st.header("Marketing Team Dispatch Planner")
297
+
298
+ st.subheader("Villages Needing Immediate Attention")
299
+
300
+ # Find villages with no recent sales or low conversion
301
+ high_priority = village_performance[
302
+ (village_performance['Days_Since_Last_Sale'] > 30) |
303
+ (village_performance['Conversion_Rate'] < 20)
304
+ ]
305
+
306
+ if not high_priority.empty:
307
+ for _, village in high_priority.iterrows():
308
+ with st.expander(f"{village['Village']} (Last sale: {village['Days_Since_Last_Sale']} days ago)"):
309
+ st.write(f"**Mantri:** {village['Mantri_Name']} ({village['Mantri_Mobile']})")
310
+ st.write(f"**Conversion Rate:** {village['Conversion_Rate']}%")
311
+ st.write(f"**Recommended Action:** Conduct demo sessions and awareness campaign")
312
+
313
+ if st.button(f"Dispatch Team to {village['Village']}", key=f"dispatch_{village['Village']}"):
314
+ st.success(f"Team dispatched to {village['Village']}. Mantri {village['Mantri_Name']} has been notified.")
315
+ else:
316
+ st.info("No villages currently require immediate team dispatch.")
317
+
318
+ st.subheader("Create New Dispatch Plan")
319
+
320
+ col1, col2 = st.columns(2)
321
+
322
+ with col1:
323
+ selected_village = st.selectbox("Select Village for Dispatch", village_performance['Village'].unique())
324
+ village_data = village_performance[village_performance['Village'] == selected_village].iloc[0]
325
+
326
+ st.write(f"**Mantri:** {village_data['Mantri_Name']}")
327
+ st.write(f"**Last Sale:** {village_data['Days_Since_Last_Sale']} days ago")
328
+ st.write(f"**Conversion Rate:** {village_data['Conversion_Rate']}%")
329
+
330
+ with col2:
331
+ dispatch_date = st.date_input("Dispatch Date", datetime.now() + timedelta(days=1))
332
+ team_size = st.slider("Team Size", 1, 5, 2)
333
+ duration = st.selectbox("Duration", ["1 day", "2 days", "3 days", "1 week"])
334
+
335
+ objectives = st.text_area("Objectives", "Conduct demo sessions, educate farmers about benefits, collect feedback")
336
+
337
+ if st.button("Schedule Dispatch"):
338
+ st.success(f"Dispatch to {selected_village} scheduled for {dispatch_date}")
339
+ st.json({
340
+ "village": selected_village,
341
+ "mantri": village_data['Mantri_Name'],
342
+ "date": str(dispatch_date),
343
+ "team_size": team_size,
344
+ "duration": duration,
345
+ "objectives": objectives
346
+ })
347
+
348
+ # Footer
349
+ st.markdown("---")
350
+ st.markdown("**Calcium Supplement Sales Automation System** | For internal use only")
data/AMBERAVPURA ENGLISH SABHASAD LIST.xlsx ADDED
Binary file (14 kB). View file
 
data/APRIL 24-25.xlsx ADDED
Binary file (31.2 kB). View file
 
data/AUGUST 24-25.xlsx ADDED
Binary file (33.1 kB). View file
 
data/JULY 24-25.xlsx ADDED
Binary file (35.3 kB). View file
 
data/JUNE 24-25.xlsx ADDED
Binary file (30 kB). View file
 
data/MAY 24-25.xlsx ADDED
Binary file (29.3 kB). View file
 
data/SEPTEMBER 24-25.xlsx ADDED
Binary file (42.1 kB). View file
 
data/amiyad.xlsx ADDED
Binary file (41.6 kB). View file
 
data/dharkhuniya.xlsx ADDED
Binary file (37.2 kB). View file
 
data/distributors.xlsx ADDED
Binary file (26.3 kB). View file
 
data/kamrol.xlsx ADDED
Binary file (39 kB). View file
 
data/sandha.xlsx ADDED
Binary file (37.5 kB). View file
 
data/vishnoli.xlsx ADDED
Binary file (38.2 kB). View file
 
pages/__init__.py ADDED
File without changes
pages/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (138 Bytes). View file
 
pages/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (142 Bytes). View file
 
pages/__pycache__/customers.cpython-310.pyc ADDED
Binary file (13.4 kB). View file
 
pages/__pycache__/dashboard.cpython-310.pyc ADDED
Binary file (702 Bytes). View file
 
pages/__pycache__/dashboard.cpython-313.pyc ADDED
Binary file (7.01 kB). View file
 
pages/__pycache__/data_import.cpython-310.pyc ADDED
Binary file (3.28 kB). View file
 
pages/__pycache__/demos.cpython-310.pyc ADDED
Binary file (16.8 kB). View file
 
pages/__pycache__/distributors.cpython-310.pyc ADDED
Binary file (27.6 kB). View file
 
pages/__pycache__/file_viewer.cpython-310.pyc ADDED
Binary file (11.9 kB). View file
 
pages/__pycache__/payments.cpython-310.pyc ADDED
Binary file (16.1 kB). View file
 
pages/__pycache__/reports.cpython-310.pyc ADDED
Binary file (31 kB). View file
 
pages/__pycache__/sales.cpython-310.pyc ADDED
Binary file (14.1 kB). View file
 
pages/__pycache__/system_dashboard.cpython-310.pyc ADDED
Binary file (4.07 kB). View file
 
pages/customers.py ADDED
@@ -0,0 +1,449 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # pages/customers.py
2
+ import streamlit as st
3
+ import pandas as pd
4
+ import plotly.express as px
5
+ from datetime import datetime, timedelta
6
+
7
+ def show_customers_page(db, whatsapp_manager=None):
8
+ """Show customer analytics, segmentation, and action planning page"""
9
+ st.title("👥 Customer Intelligence Center")
10
+
11
+ if not db:
12
+ st.error("Database not available. Please check initialization.")
13
+ return
14
+
15
+ # Tabs for different customer functions
16
+ tab1, tab2, tab3, tab4 = st.tabs(["📊 Dashboard", "🎯 Segmentation", "📞 Action Center", "🔍 Customer Directory"])
17
+
18
+ with tab1:
19
+ show_customer_dashboard_tab(db)
20
+
21
+ with tab2:
22
+ show_customer_segmentation_tab(db)
23
+
24
+ with tab3:
25
+ show_action_center_tab(db, whatsapp_manager)
26
+
27
+ with tab4:
28
+ show_customer_directory_tab(db)
29
+
30
+ def show_customer_dashboard_tab(db):
31
+ """Show customer analytics dashboard"""
32
+ st.subheader("📊 Customer Analytics Dashboard")
33
+
34
+ try:
35
+ # Get comprehensive customer data
36
+ customers_data = get_customer_analytics_data(db)
37
+
38
+ if customers_data.empty:
39
+ st.info("No customer data available yet.")
40
+ return
41
+
42
+ # Key Metrics
43
+ st.subheader("🎯 Key Metrics")
44
+ col1, col2, col3, col4 = st.columns(4)
45
+
46
+ with col1:
47
+ total_customers = len(customers_data)
48
+ st.metric("Total Customers", total_customers)
49
+
50
+ with col2:
51
+ active_customers = len(customers_data[customers_data['total_purchases'] > 0])
52
+ st.metric("Active Customers", active_customers)
53
+
54
+ with col3:
55
+ avg_purchase_value = customers_data[customers_data['total_spent'] > 0]['total_spent'].mean()
56
+ st.metric("Avg Purchase Value", f"₹{avg_purchase_value:,.0f}" if not pd.isna(avg_purchase_value) else "₹0")
57
+
58
+ with col4:
59
+ repeat_customers = len(customers_data[customers_data['total_purchases'] > 1])
60
+ st.metric("Repeat Customers", repeat_customers)
61
+
62
+ # Village-wise Analysis
63
+ st.subheader("🗺️ Geographic Distribution")
64
+ col1, col2 = st.columns(2)
65
+
66
+ with col1:
67
+ village_stats = customers_data.groupby('village').agg({
68
+ 'customer_id': 'count',
69
+ 'total_spent': 'sum',
70
+ 'total_purchases': 'sum'
71
+ }).reset_index()
72
+ village_stats.columns = ['Village', 'Customers', 'Total Revenue', 'Total Purchases']
73
+ village_stats = village_stats.sort_values('Customers', ascending=False)
74
+
75
+ if not village_stats.empty:
76
+ fig = px.bar(village_stats.head(10), x='Village', y='Customers',
77
+ title='Top 10 Villages by Customer Count',
78
+ color='Customers')
79
+ st.plotly_chart(fig, use_container_width=True)
80
+
81
+ with col2:
82
+ if not village_stats.empty:
83
+ fig = px.pie(village_stats.head(8), values='Customers', names='Village',
84
+ title='Customer Distribution by Village')
85
+ st.plotly_chart(fig, use_container_width=True)
86
+
87
+ # Purchase Behavior
88
+ st.subheader("💰 Purchase Behavior Analysis")
89
+ col1, col2 = st.columns(2)
90
+
91
+ with col1:
92
+ # Customer lifetime value distribution
93
+ spending_brackets = customers_data[customers_data['total_spent'] > 0]['total_spent']
94
+ if not spending_brackets.empty:
95
+ fig = px.histogram(spending_brackets, nbins=10,
96
+ title='Customer Spending Distribution',
97
+ labels={'value': 'Total Spent (₹)', 'count': 'Number of Customers'})
98
+ st.plotly_chart(fig, use_container_width=True)
99
+
100
+ with col2:
101
+ # Recency analysis
102
+ if 'last_purchase_date' in customers_data.columns:
103
+ recent_customers = customers_data[customers_data['last_purchase_date'].notna()]
104
+ if not recent_customers.empty:
105
+ recent_customers['days_since_purchase'] = (datetime.now() - pd.to_datetime(recent_customers['last_purchase_date'])).dt.days
106
+ fig = px.histogram(recent_customers, x='days_since_purchase', nbins=10,
107
+ title='Days Since Last Purchase',
108
+ labels={'days_since_purchase': 'Days', 'count': 'Customers'})
109
+ st.plotly_chart(fig, use_container_width=True)
110
+
111
+ except Exception as e:
112
+ st.error(f"Error loading customer analytics: {e}")
113
+
114
+ def show_customer_segmentation_tab(db):
115
+ """Show customer segmentation and targeting"""
116
+ st.subheader("🎯 Customer Segmentation")
117
+
118
+ try:
119
+ customers_data = get_customer_analytics_data(db)
120
+
121
+ if customers_data.empty:
122
+ st.info("No customer data available for segmentation.")
123
+ return
124
+
125
+ # Segmentation criteria
126
+ st.subheader("🔍 Define Segments")
127
+
128
+ col1, col2 = st.columns(2)
129
+
130
+ with col1:
131
+ segment_by = st.selectbox("Segment By",
132
+ ["Purchase Behavior", "Geographic", "Demographic", "Custom"])
133
+
134
+ if segment_by == "Purchase Behavior":
135
+ min_purchases = st.slider("Minimum Purchases", 0, 20, 1)
136
+ min_spent = st.number_input("Minimum Amount Spent (₹)", 0, 100000, 1000)
137
+
138
+ segment_customers = customers_data[
139
+ (customers_data['total_purchases'] >= min_purchases) &
140
+ (customers_data['total_spent'] >= min_spent)
141
+ ]
142
+
143
+ elif segment_by == "Geographic":
144
+ selected_villages = st.multiselect("Select Villages",
145
+ customers_data['village'].unique())
146
+ if selected_villages:
147
+ segment_customers = customers_data[customers_data['village'].isin(selected_villages)]
148
+ else:
149
+ segment_customers = customers_data
150
+
151
+ elif segment_by == "Demographic":
152
+ # Add demographic filters here
153
+ segment_customers = customers_data
154
+
155
+ with col2:
156
+ st.write("**Segment Actions**")
157
+ segment_size = len(segment_customers)
158
+ st.metric("Segment Size", segment_size)
159
+
160
+ if segment_size > 0:
161
+ avg_segment_value = segment_customers['total_spent'].mean()
162
+ st.metric("Avg Customer Value", f"₹{avg_segment_value:,.0f}")
163
+
164
+ # Quick actions for segment
165
+ if st.button("📱 Send Bulk WhatsApp", key="segment_whatsapp"):
166
+ st.info(f"Ready to send message to {segment_size} customers")
167
+
168
+ if st.button("📍 Plan Field Visit", key="segment_visit"):
169
+ villages = segment_customers['village'].value_counts().head(5)
170
+ st.success(f"Top villages to visit: {', '.join(villages.index.tolist())}")
171
+
172
+ # Show segment details
173
+ if not segment_customers.empty:
174
+ st.subheader("👥 Segment Details")
175
+
176
+ # Segment characteristics
177
+ col1, col2, col3 = st.columns(3)
178
+
179
+ with col1:
180
+ top_village = segment_customers['village'].mode()[0] if not segment_customers['village'].mode().empty else "N/A"
181
+ st.metric("Most Common Village", top_village)
182
+
183
+ with col2:
184
+ avg_purchases = segment_customers['total_purchases'].mean()
185
+ st.metric("Avg Purchases/Customer", f"{avg_purchases:.1f}")
186
+
187
+ with col3:
188
+ total_potential = segment_customers['total_spent'].sum()
189
+ st.metric("Segment Total Value", f"₹{total_potential:,.0f}")
190
+
191
+ # Customer list in segment
192
+ st.dataframe(segment_customers[['name', 'village', 'mobile', 'total_purchases', 'total_spent']].head(20),
193
+ use_container_width=True)
194
+
195
+ except Exception as e:
196
+ st.error(f"Error in customer segmentation: {e}")
197
+
198
+ def show_action_center_tab(db, whatsapp_manager):
199
+ """Show action planning and communication center"""
200
+ st.subheader("📞 Customer Action Center")
201
+
202
+ try:
203
+ customers_data = get_customer_analytics_data(db)
204
+
205
+ if customers_data.empty:
206
+ st.info("No customer data available for actions.")
207
+ return
208
+
209
+ # Action categories
210
+ action_type = st.selectbox("Action Type",
211
+ ["Follow-up Calls", "Demo Follow-ups", "Payment Reminders",
212
+ "New Product Announcements", "Customer Feedback"])
213
+
214
+ if action_type == "Follow-up Calls":
215
+ show_followup_calls_section(db, customers_data)
216
+
217
+ elif action_type == "Demo Follow-ups":
218
+ show_demo_followups_section(db, customers_data)
219
+
220
+ elif action_type == "Payment Reminders":
221
+ show_payment_reminders_section(db, customers_data, whatsapp_manager)
222
+
223
+ elif action_type == "New Product Announcements":
224
+ show_product_announcements_section(db, customers_data, whatsapp_manager)
225
+
226
+ elif action_type == "Customer Feedback":
227
+ show_feedback_section(db, customers_data, whatsapp_manager)
228
+
229
+ except Exception as e:
230
+ st.error(f"Error in action center: {e}")
231
+
232
+ def show_followup_calls_section(db, customers_data):
233
+ """Show customers needing follow-up calls"""
234
+ st.write("### 📞 Customers Needing Follow-up")
235
+
236
+ # Identify customers for follow-up
237
+ follow_up_criteria = st.multiselect("Follow-up Criteria",
238
+ ["No Purchase in 30 days", "High Value Customers",
239
+ "Single Purchase Only", "Specific Villages"])
240
+
241
+ target_customers = customers_data.copy()
242
+
243
+ if "No Purchase in 30 days" in follow_up_criteria:
244
+ # This would require last_purchase_date in your data
245
+ st.info("Last purchase date tracking needed for this feature")
246
+
247
+ if "High Value Customers" in follow_up_criteria:
248
+ high_value_threshold = st.number_input("High Value Threshold (₹)", 1000, 10000, 5000)
249
+ target_customers = target_customers[target_customers['total_spent'] >= high_value_threshold]
250
+
251
+ if "Single Purchase Only" in follow_up_criteria:
252
+ target_customers = target_customers[target_customers['total_purchases'] == 1]
253
+
254
+ if not target_customers.empty:
255
+ st.write(f"**{len(target_customers)} customers identified for follow-up**")
256
+
257
+ # Village concentration
258
+ village_concentration = target_customers['village'].value_counts().head(5)
259
+ st.write("**Top villages for field visits:**")
260
+ for village, count in village_concentration.items():
261
+ st.write(f"- {village}: {count} customers")
262
+
263
+ # Display customer list
264
+ st.dataframe(target_customers[['name', 'village', 'mobile', 'total_purchases', 'total_spent']],
265
+ use_container_width=True)
266
+
267
+ def show_demo_followups_section(db, customers_data):
268
+ """Show demo conversion tracking"""
269
+ st.write("### 🎯 Demo Conversion Tracking")
270
+
271
+ try:
272
+ # Get demo data
273
+ demo_data = db.get_dataframe('demos', '''
274
+ SELECT d.*, c.name as customer_name, c.village, c.mobile, p.product_name
275
+ FROM demos d
276
+ LEFT JOIN customers c ON d.customer_id = c.customer_id
277
+ LEFT JOIN products p ON d.product_id = p.product_id
278
+ ORDER BY d.demo_date DESC
279
+ ''')
280
+
281
+ if not demo_data.empty:
282
+ # Demo conversion stats
283
+ total_demos = len(demo_data)
284
+ converted_demos = len(demo_data[demo_data['conversion_status'] == 'Converted'])
285
+ conversion_rate = (converted_demos / total_demos) * 100 if total_demos > 0 else 0
286
+
287
+ col1, col2, col3 = st.columns(3)
288
+ with col1:
289
+ st.metric("Total Demos", total_demos)
290
+ with col2:
291
+ st.metric("Converted", converted_demos)
292
+ with col3:
293
+ st.metric("Conversion Rate", f"{conversion_rate:.1f}%")
294
+
295
+ # Pending follow-ups
296
+ pending_followups = demo_data[
297
+ (demo_data['conversion_status'] == 'Not Converted') &
298
+ (pd.to_datetime(demo_data['follow_up_date']) <= datetime.now())
299
+ ]
300
+
301
+ if not pending_followups.empty:
302
+ st.warning(f"🚨 {len(pending_followups)} demos need immediate follow-up!")
303
+ st.dataframe(pending_followups[['customer_name', 'village', 'product_name', 'demo_date', 'follow_up_date']],
304
+ use_container_width=True)
305
+
306
+ # All demo records
307
+ st.write("**All Demo Records**")
308
+ st.dataframe(demo_data[['customer_name', 'village', 'product_name', 'demo_date', 'conversion_status']],
309
+ use_container_width=True)
310
+ else:
311
+ st.info("No demo records found.")
312
+
313
+ except Exception as e:
314
+ st.error(f"Error loading demo data: {e}")
315
+
316
+ def show_payment_reminders_section(db, customers_data, whatsapp_manager):
317
+ """Show payment reminder system"""
318
+ st.write("### 💰 Payment Reminders")
319
+
320
+ try:
321
+ # Get pending payments
322
+ pending_payments = db.get_pending_payments()
323
+
324
+ if not pending_payments.empty:
325
+ total_pending = pending_payments['pending_amount'].sum()
326
+ st.metric("Total Pending Amount", f"₹{total_pending:,.2f}")
327
+
328
+ # Group by customer
329
+ customer_pending = pending_payments.groupby('customer_name').agg({
330
+ 'pending_amount': 'sum',
331
+ 'invoice_no': 'count'
332
+ }).reset_index()
333
+ customer_pending.columns = ['Customer', 'Total Pending', 'Pending Invoices']
334
+ customer_pending = customer_pending.sort_values('Total Pending', ascending=False)
335
+
336
+ st.dataframe(customer_pending, use_container_width=True)
337
+
338
+ # Bulk WhatsApp reminders
339
+ st.write("**Bulk Payment Reminders**")
340
+ if st.button("📱 Send Payment Reminders to All", type="primary") and whatsapp_manager:
341
+ st.info("This would send payment reminders to all customers with pending payments")
342
+ else:
343
+ st.success("🎉 All payments are cleared! No pending payments.")
344
+
345
+ except Exception as e:
346
+ st.error(f"Error loading payment data: {e}")
347
+
348
+ def show_product_announcements_section(db, customers_data, whatsapp_manager):
349
+ """Show new product announcement system"""
350
+ st.write("### 🆕 New Product Announcements")
351
+
352
+ # Target segments for new products
353
+ segment = st.selectbox("Target Segment",
354
+ ["All Customers", "High Value Customers", "Specific Village", "Previous Product Buyers"])
355
+
356
+ message_template = st.text_area("Announcement Message",
357
+ height=100,
358
+ value="Hello {name}! We have exciting new products available. Reply YES for details!")
359
+
360
+ if st.button("📢 Send Announcement", type="primary") and whatsapp_manager:
361
+ st.success("Ready to send announcement to selected segment!")
362
+
363
+ def show_feedback_section(db, customers_data, whatsapp_manager):
364
+ """Show customer feedback collection system"""
365
+ st.write("### 💬 Customer Feedback Collection")
366
+
367
+ feedback_segment = st.selectbox("Request Feedback From",
368
+ ["Recent Customers", "High Value Customers", "Inactive Customers"])
369
+
370
+ feedback_message = st.text_area("Feedback Request Message",
371
+ height=100,
372
+ value="Hello {name}! We value your feedback. How was your experience with us?")
373
+
374
+ if st.button("📝 Request Feedback", type="primary") and whatsapp_manager:
375
+ st.info("Feedback requests ready to send!")
376
+
377
+ def show_customer_directory_tab(db):
378
+ """Show comprehensive customer directory"""
379
+ st.subheader("🔍 Customer Directory")
380
+
381
+ try:
382
+ customers_data = get_customer_analytics_data(db)
383
+
384
+ if customers_data.empty:
385
+ st.info("No customers found in the database.")
386
+ return
387
+
388
+ # Filters
389
+ col1, col2, col3 = st.columns(3)
390
+
391
+ with col1:
392
+ village_filter = st.multiselect("Filter by Village", customers_data['village'].unique())
393
+
394
+ with col2:
395
+ purchase_filter = st.selectbox("Filter by Purchase History",
396
+ ["All", "Has Purchases", "No Purchases", "Multiple Purchases"])
397
+
398
+ with col3:
399
+ search_term = st.text_input("Search by Name/Mobile")
400
+
401
+ # Apply filters
402
+ filtered_customers = customers_data.copy()
403
+
404
+ if village_filter:
405
+ filtered_customers = filtered_customers[filtered_customers['village'].isin(village_filter)]
406
+
407
+ if purchase_filter == "Has Purchases":
408
+ filtered_customers = filtered_customers[filtered_customers['total_purchases'] > 0]
409
+ elif purchase_filter == "No Purchases":
410
+ filtered_customers = filtered_customers[filtered_customers['total_purchases'] == 0]
411
+ elif purchase_filter == "Multiple Purchases":
412
+ filtered_customers = filtered_customers[filtered_customers['total_purchases'] > 1]
413
+
414
+ if search_term:
415
+ filtered_customers = filtered_customers[
416
+ filtered_customers['name'].str.contains(search_term, case=False, na=False) |
417
+ filtered_customers['mobile'].str.contains(search_term, na=False)
418
+ ]
419
+
420
+ # Display results
421
+ st.write(f"**Found {len(filtered_customers)} customers**")
422
+
423
+ display_columns = ['name', 'village', 'mobile', 'total_purchases', 'total_spent']
424
+ display_df = filtered_customers[display_columns]
425
+ display_df.columns = ['Name', 'Village', 'Mobile', 'Total Purchases', 'Total Spent (₹)']
426
+ display_df['Total Spent (₹)'] = display_df['Total Spent (₹)'].apply(lambda x: f"₹{x:,.0f}")
427
+
428
+ st.dataframe(display_df, use_container_width=True)
429
+
430
+ except Exception as e:
431
+ st.error(f"Error loading customer directory: {e}")
432
+
433
+ def get_customer_analytics_data(db):
434
+ """Get comprehensive customer data with analytics"""
435
+ try:
436
+ customers = db.get_dataframe('customers', '''
437
+ SELECT c.*,
438
+ COUNT(s.sale_id) as total_purchases,
439
+ COALESCE(SUM(s.total_amount), 0) as total_spent,
440
+ MAX(s.sale_date) as last_purchase_date
441
+ FROM customers c
442
+ LEFT JOIN sales s ON c.customer_id = s.customer_id
443
+ GROUP BY c.customer_id
444
+ ORDER BY total_spent DESC
445
+ ''')
446
+ return customers
447
+ except Exception as e:
448
+ st.error(f"Error loading customer analytics data: {e}")
449
+ return pd.DataFrame()
pages/dashboard.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import streamlit.components.v1 as components
3
+
4
+ def create_dashboard(db=None, analytics=None):
5
+ # DO NOT call st.set_page_config here
6
+ st.markdown("<h1 class='main-header'>📊 Power BI Dashboard</h1>", unsafe_allow_html=True)
7
+ components.iframe(
8
+ src="https://app.powerbi.com/view?r=eyJrIjoiM2VmZDQxNTUtMGEyYS00NDNiLWEyMDMtZWY5MGFkYTlmYjU2IiwidCI6ImFmYTM1MTRhLTFlNDItNDBjOS04ZjExLWIzODNlNmRhYTM3NiIsImMiOjN9",
9
+ width=1200,
10
+ height=800,
11
+ scrolling=True
12
+ )
pages/data_import.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # pages/data_import.py
2
+ import streamlit as st
3
+ import os
4
+ import glob
5
+ from datetime import datetime
6
+
7
+ def show_data_import_page(db, data_processor):
8
+ """Show data import page"""
9
+ st.title("📤 Data Import & Processing")
10
+
11
+ if not data_processor:
12
+ st.error("Data processor not available. Please check initialization.")
13
+ return
14
+
15
+ data_dir = "data"
16
+ if os.path.exists(data_dir):
17
+ excel_files = glob.glob(os.path.join(data_dir, "*.xlsx")) + glob.glob(os.path.join(data_dir, "*.xls"))
18
+
19
+ if excel_files:
20
+ st.subheader("📁 Existing Files in Data Folder")
21
+
22
+ for file_path in excel_files:
23
+ file_name = os.path.basename(file_path)
24
+ file_size = os.path.getsize(file_path) / 1024
25
+ file_mtime = datetime.fromtimestamp(os.path.getmtime(file_path))
26
+
27
+ col1, col2, col3 = st.columns([3, 2, 1])
28
+ with col1:
29
+ st.write(f"**{file_name}**")
30
+ st.write(f"Size: {file_size:.1f} KB | Modified: {file_mtime.strftime('%Y-%m-%d %H:%M')}")
31
+ with col2:
32
+ if st.button(f"🔄 Process", key=f"process_{file_name}"):
33
+ try:
34
+ if data_processor.process_excel_file(file_path):
35
+ st.success(f"✅ Processed: {file_name}")
36
+ st.rerun()
37
+ else:
38
+ st.warning(f"⚠️ No data processed from: {file_name}")
39
+ except Exception as e:
40
+ st.error(f"❌ Error processing {file_name}: {str(e)}")
41
+ with col3:
42
+ if st.button(f"🗑️ Delete", key=f"delete_{file_name}"):
43
+ try:
44
+ os.remove(file_path)
45
+ st.success(f"✅ Deleted: {file_name}")
46
+ st.rerun()
47
+ except Exception as e:
48
+ st.error(f"Error deleting file: {e}")
49
+
50
+ if st.button("🔄 Process All Files", type="primary"):
51
+ success_count = 0
52
+ for file_path in excel_files:
53
+ try:
54
+ if data_processor.process_excel_file(file_path):
55
+ success_count += 1
56
+ except Exception as e:
57
+ st.error(f"Error processing {os.path.basename(file_path)}: {str(e)}")
58
+ st.success(f"✅ Processed {success_count}/{len(excel_files)} files successfully!")
59
+ if success_count > 0:
60
+ st.rerun()
61
+ else:
62
+ st.info("No Excel files found in the data folder.")
63
+ else:
64
+ os.makedirs(data_dir, exist_ok=True)
65
+ st.info("Data folder created. Upload Excel files to get started.")
66
+
67
+ st.subheader("📤 Upload New Excel File")
68
+ uploaded_file = st.file_uploader("Choose Excel file", type=['xlsx', 'xls'])
69
+
70
+ if uploaded_file:
71
+ file_path = os.path.join("data", uploaded_file.name)
72
+ with open(file_path, "wb") as f:
73
+ f.write(uploaded_file.getbuffer())
74
+
75
+ st.success(f"✅ File saved: {uploaded_file.name}")
76
+
77
+ if st.button(f"🔄 Process {uploaded_file.name}"):
78
+ try:
79
+ if data_processor.process_excel_file(file_path):
80
+ st.success(f"✅ Processed: {uploaded_file.name}")
81
+
82
+ # Show data preview
83
+ st.subheader("📊 Imported Data Preview")
84
+ try:
85
+ customers = db.get_dataframe('customers')
86
+ sales = db.get_dataframe('sales')
87
+
88
+ col1, col2 = st.columns(2)
89
+ with col1:
90
+ st.metric("Customers", len(customers))
91
+ if not customers.empty:
92
+ st.dataframe(customers.tail(3), use_container_width=True)
93
+ with col2:
94
+ st.metric("Sales", len(sales))
95
+ if not sales.empty:
96
+ st.dataframe(sales.tail(3), use_container_width=True)
97
+ except Exception as e:
98
+ st.error(f"Error loading preview data: {e}")
99
+
100
+ st.rerun()
101
+ else:
102
+ st.warning(f"⚠️ No data processed from: {uploaded_file.name}")
103
+ except Exception as e:
104
+ st.error(f"❌ Error processing {uploaded_file.name}: {str(e)}")
pages/demos.py ADDED
@@ -0,0 +1,824 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # pages/demos.py
2
+ import streamlit as st
3
+ import pandas as pd
4
+ import plotly.express as px
5
+ from datetime import datetime, timedelta
6
+ import time
7
+
8
+
9
+ def show_demos_page(db, whatsapp_manager=None):
10
+ """Show demo management and tracking page"""
11
+ st.title("🎯 Demo Management Center")
12
+
13
+ if not db:
14
+ st.error("Database not available. Please check initialization.")
15
+ return
16
+
17
+ # Tabs for different demo functions
18
+ tab1, tab2, tab3, tab4 = st.tabs(
19
+ ["➕ Schedule Demo", "📋 Demo Calendar", "📊 Demo Analytics", "🔄 Follow-ups"]
20
+ )
21
+
22
+ with tab1:
23
+ show_schedule_demo_tab(db, whatsapp_manager)
24
+
25
+ with tab2:
26
+ show_demo_calendar_tab(db)
27
+
28
+ with tab3:
29
+ show_demo_analytics_tab(db)
30
+
31
+ with tab4:
32
+ show_follow_ups_tab(db, whatsapp_manager)
33
+
34
+
35
+ def show_schedule_demo_tab(db, whatsapp_manager):
36
+ """Show form to schedule new demos"""
37
+ st.subheader("➕ Schedule New Demo")
38
+
39
+ # Show demo summary if we just created one
40
+ if "last_demo_id" in st.session_state and st.session_state.last_demo_id:
41
+ show_demo_summary(db, st.session_state.last_demo_id)
42
+ st.session_state.last_demo_id = None # Clear after showing
43
+ st.divider()
44
+
45
+ with st.form("schedule_demo_form"):
46
+ st.markdown("### 👥 Demo Information")
47
+
48
+ col1, col2 = st.columns(2)
49
+
50
+ with col1:
51
+ # Customer selection
52
+ customers = db.get_dataframe(
53
+ "customers", "SELECT customer_id, name, village FROM customers"
54
+ )
55
+ if not customers.empty:
56
+ customer_options = {
57
+ f"{row['name']} ({row['village']})": row["customer_id"]
58
+ for _, row in customers.iterrows()
59
+ }
60
+ selected_customer = st.selectbox(
61
+ "Select Customer*", options=list(customer_options.keys())
62
+ )
63
+ customer_id = (
64
+ customer_options[selected_customer] if selected_customer else None
65
+ )
66
+ else:
67
+ st.warning("No customers found. Please add customers first.")
68
+ customer_id = None
69
+
70
+ # Distributor selection
71
+ distributors = db.get_dataframe(
72
+ "distributors", "SELECT distributor_id, name, village FROM distributors"
73
+ )
74
+ if not distributors.empty:
75
+ distributor_options = {
76
+ f"{row['name']} ({row['village']})": row["distributor_id"]
77
+ for _, row in distributors.iterrows()
78
+ }
79
+ selected_distributor = st.selectbox(
80
+ "Assign Distributor",
81
+ options=[""] + list(distributor_options.keys()),
82
+ )
83
+ distributor_id = (
84
+ distributor_options[selected_distributor]
85
+ if selected_distributor
86
+ else None
87
+ )
88
+ else:
89
+ distributor_id = None
90
+
91
+ with col2:
92
+ # Product selection
93
+ products = db.get_dataframe(
94
+ "products",
95
+ "SELECT product_id, product_name FROM products WHERE is_active = 1",
96
+ )
97
+ if not products.empty:
98
+ product_options = {
99
+ row["product_name"]: row["product_id"]
100
+ for _, row in products.iterrows()
101
+ }
102
+ selected_product = st.selectbox(
103
+ "Product to Demo*", options=list(product_options.keys())
104
+ )
105
+ product_id = (
106
+ product_options[selected_product] if selected_product else None
107
+ )
108
+ else:
109
+ st.warning("No products found. Please add products first.")
110
+ product_id = None
111
+
112
+ # Demo details
113
+ demo_date = st.date_input("Demo Date*", datetime.now().date())
114
+ demo_time = st.time_input("Demo Time", datetime.now().time())
115
+
116
+ st.markdown("### 📝 Demo Details")
117
+
118
+ col1, col2 = st.columns(2)
119
+
120
+ with col1:
121
+ quantity_provided = st.number_input(
122
+ "Quantity Provided",
123
+ min_value=0,
124
+ value=1,
125
+ help="Number of units provided for demo",
126
+ )
127
+ demo_location = st.selectbox(
128
+ "Demo Location",
129
+ ["Customer Home", "Distributor Office", "Public Place", "Other"],
130
+ )
131
+
132
+ with col2:
133
+ follow_up_date = st.date_input(
134
+ "Follow-up Date", datetime.now().date() + timedelta(days=7)
135
+ )
136
+ conversion_status = st.selectbox(
137
+ "Initial Status", ["Scheduled", "Completed", "Cancelled"]
138
+ )
139
+
140
+ notes = st.text_area(
141
+ "Demo Notes",
142
+ placeholder="Any special instructions, customer requirements, or observations...",
143
+ )
144
+
145
+ # Submit button
146
+ submitted = st.form_submit_button(
147
+ "🎯 Schedule Demo",
148
+ type="primary",
149
+ )
150
+
151
+ # Handle form submission OUTSIDE the form to prevent resubmission
152
+ if submitted:
153
+ # Create unique submission ID to prevent duplicates
154
+ submission_id = f"{customer_id}_{product_id}_{demo_date}_{demo_time}_{int(time.time())}"
155
+
156
+ # Initialize submission tracking if not exists
157
+ if "processed_submissions" not in st.session_state:
158
+ st.session_state.processed_submissions = set()
159
+
160
+ # Check if this exact submission was already processed in this session
161
+ if submission_id in st.session_state.processed_submissions:
162
+ st.warning("⚠️ This demo was already submitted. Showing previously created demo.")
163
+ # Don't rerun, just show the existing demo
164
+ else:
165
+ # Validation
166
+ errors = []
167
+ if not customer_id:
168
+ errors.append("Customer selection is required")
169
+ if not product_id:
170
+ errors.append("Product selection is required")
171
+ if not demo_date:
172
+ errors.append("Demo date is required")
173
+
174
+ if errors:
175
+ for error in errors:
176
+ st.error(f"❌ {error}")
177
+ else:
178
+ # Check if a similar demo already exists in database (within last 5 minutes)
179
+ try:
180
+ recent_demos = db.get_dataframe(
181
+ "demos",
182
+ """
183
+ SELECT demo_id FROM demos
184
+ WHERE customer_id = ?
185
+ AND product_id = ?
186
+ AND demo_date = ?
187
+ AND created_date >= datetime('now', '-5 minutes')
188
+ ORDER BY demo_id DESC LIMIT 1
189
+ """,
190
+ params=(customer_id, product_id, demo_date)
191
+ )
192
+
193
+ if not recent_demos.empty:
194
+ existing_demo_id = recent_demos.iloc[0]['demo_id']
195
+ st.warning(f"⚠️ A similar demo (ID: {existing_demo_id}) was already created recently. Showing that demo instead.")
196
+ st.session_state.last_demo_id = existing_demo_id
197
+ st.session_state.processed_submissions.add(submission_id)
198
+ st.rerun()
199
+ return
200
+ except Exception as check_error:
201
+ st.warning(f"Could not check for existing demos: {check_error}")
202
+
203
+ try:
204
+ # Ensure demo_date is a single date object (not tuple)
205
+ if isinstance(demo_date, tuple):
206
+ demo_date = demo_date[0] if demo_date else datetime.now().date()
207
+
208
+ # Ensure follow_up_date is a single date object
209
+ if isinstance(follow_up_date, tuple):
210
+ follow_up_date = follow_up_date[0] if follow_up_date else datetime.now().date() + timedelta(days=7)
211
+
212
+ # Combine date and time for notification
213
+ demo_datetime = datetime.combine(demo_date, demo_time)
214
+
215
+ # Add demo to database
216
+ demo_id = add_demo_to_database(
217
+ db,
218
+ {
219
+ "customer_id": customer_id,
220
+ "distributor_id": distributor_id,
221
+ "product_id": product_id,
222
+ "demo_date": demo_date,
223
+ "demo_time": demo_time,
224
+ "quantity_provided": quantity_provided,
225
+ "follow_up_date": follow_up_date,
226
+ "conversion_status": conversion_status,
227
+ "notes": notes,
228
+ "demo_location": demo_location,
229
+ },
230
+ )
231
+
232
+ if demo_id and demo_id > 0:
233
+ # Mark this submission as processed
234
+ st.session_state.processed_submissions.add(submission_id)
235
+
236
+ st.success(
237
+ f"✅ Demo scheduled successfully! Demo ID: {demo_id}"
238
+ )
239
+
240
+ # Send notification if WhatsApp available
241
+ if whatsapp_manager and customer_id:
242
+ send_demo_notification(
243
+ whatsapp_manager,
244
+ db,
245
+ customer_id,
246
+ demo_datetime,
247
+ product_id,
248
+ )
249
+
250
+ # Store demo_id in session state to show summary outside form
251
+ st.session_state.last_demo_id = demo_id
252
+
253
+ # Set flag for dashboard notification
254
+ st.session_state.demo_created_notification = demo_id
255
+
256
+ # Update refresh time to ensure dashboard shows latest demos
257
+ st.session_state.demo_refresh_time = time.time()
258
+
259
+ # Rerun to show summary
260
+ st.rerun()
261
+
262
+ else:
263
+ st.error("❌ Failed to schedule demo. Please try again.")
264
+
265
+ except Exception as e:
266
+ st.error(f"❌ Error scheduling demo: {e}")
267
+ import traceback
268
+ st.code(traceback.format_exc())
269
+
270
+
271
+ def add_demo_to_database(db, demo_data):
272
+ """Add demo record to database"""
273
+ try:
274
+ # Insert the demo - execute_query returns [(lastrowid,)] for INSERT
275
+ result = db.execute_query(
276
+ """
277
+ INSERT INTO demos (customer_id, distributor_id, product_id, demo_date, demo_time,
278
+ quantity_provided, follow_up_date, conversion_status, notes, demo_location)
279
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
280
+ """,
281
+ (
282
+ demo_data["customer_id"],
283
+ demo_data["distributor_id"],
284
+ demo_data["product_id"],
285
+ demo_data["demo_date"],
286
+ demo_data["demo_time"].strftime("%H:%M:%S")
287
+ if demo_data.get("demo_time")
288
+ else None,
289
+ demo_data["quantity_provided"],
290
+ demo_data["follow_up_date"],
291
+ demo_data["conversion_status"],
292
+ demo_data["notes"],
293
+ demo_data["demo_location"],
294
+ ),
295
+ log_action=False,
296
+ )
297
+
298
+ # The execute_query method returns [(lastrowid,)] for INSERT queries
299
+ demo_id = result[0][0] if result and len(result) > 0 and result[0][0] else None
300
+
301
+ if demo_id:
302
+ return demo_id
303
+ else:
304
+ st.error("❌ Failed to get demo_id after insertion")
305
+ return -1
306
+
307
+ except Exception as e:
308
+ st.error(f"Database error: {e}")
309
+ import traceback
310
+ st.code(traceback.format_exc())
311
+ return -1
312
+
313
+
314
+ def send_demo_notification(
315
+ whatsapp_manager, db, customer_id, demo_datetime, product_id
316
+ ):
317
+ """Send demo notification to customer"""
318
+ try:
319
+ # Get customer and product details
320
+ customer = db.get_dataframe(
321
+ "customers", f"SELECT * FROM customers WHERE customer_id = {customer_id}"
322
+ )
323
+ product = db.get_dataframe(
324
+ "products", f"SELECT * FROM products WHERE product_id = {product_id}"
325
+ )
326
+
327
+ if not customer.empty and not product.empty:
328
+ customer_data = customer.iloc[0]
329
+ product_data = product.iloc[0]
330
+
331
+ if customer_data.get("mobile"):
332
+ # Format time safely
333
+ time_str = demo_datetime.strftime("%I:%M %p")
334
+
335
+ message = f"""Hello {customer_data["name"]}! 🎉
336
+
337
+ We're excited to confirm your product demo!
338
+
339
+ 📅 Date: {demo_datetime.strftime("%d %b %Y")}
340
+ ⏰ Time: {time_str}
341
+ 📦 Product: {product_data["product_name"]}
342
+
343
+ Our team will demonstrate the product features and answer any questions you may have.
344
+
345
+ We look forward to meeting you!
346
+
347
+ Best regards,
348
+ Sales Team"""
349
+
350
+ success = whatsapp_manager.send_message(
351
+ customer_data["mobile"], message
352
+ )
353
+ if success:
354
+ st.success("📱 Demo notification sent to customer!")
355
+ else:
356
+ st.warning("⚠️ Could not send demo notification")
357
+
358
+ except Exception as e:
359
+ st.warning(f"Could not send demo notification: {e}")
360
+
361
+
362
+ def show_demo_summary(db, demo_id):
363
+ """Show summary of scheduled demo"""
364
+ try:
365
+ demo_data = db.get_dataframe(
366
+ "demos",
367
+ f"""
368
+ SELECT d.*, c.name as customer_name, c.village, p.product_name,
369
+ dist.name as distributor_name
370
+ FROM demos d
371
+ LEFT JOIN customers c ON d.customer_id = c.customer_id
372
+ LEFT JOIN products p ON d.product_id = p.product_id
373
+ LEFT JOIN distributors dist ON d.distributor_id = dist.distributor_id
374
+ WHERE d.demo_id = {demo_id}
375
+ """,
376
+ )
377
+
378
+ if not demo_data.empty:
379
+ demo = demo_data.iloc[0]
380
+
381
+ st.markdown("## 🎉 Demo Scheduled Successfully!")
382
+
383
+ col1, col2 = st.columns(2)
384
+
385
+ with col1:
386
+ st.subheader("👥 Demo Details")
387
+ st.write(f"**Demo ID:** {demo_id}")
388
+ st.write(f"**Customer:** {demo['customer_name']}")
389
+ st.write(f"**Village:** {demo['village']}")
390
+ st.write(f"**Product:** {demo['product_name']}")
391
+ if demo.get("distributor_name"):
392
+ st.write(f"**Distributor:** {demo['distributor_name']}")
393
+
394
+ with col2:
395
+ st.subheader("📅 Schedule")
396
+ st.write(f"**Demo Date:** {demo['demo_date']}")
397
+ if demo.get("demo_time"):
398
+ st.write(f"**Demo Time:** {demo['demo_time']}")
399
+ st.write(f"**Follow-up Date:** {demo['follow_up_date']}")
400
+ st.write(f"**Status:** {demo['conversion_status']}")
401
+
402
+ # Quick actions
403
+ st.markdown("### ⚡ Quick Actions")
404
+ col1, col2, col3, col4 = st.columns(4)
405
+
406
+ with col1:
407
+ if st.button("🏠 Go to Dashboard"):
408
+ st.session_state.current_page = "dashboard"
409
+ st.rerun()
410
+
411
+ with col2:
412
+ if st.button("📋 View All Demos"):
413
+ st.session_state.current_tab = "📋 Demo Calendar"
414
+ st.rerun()
415
+
416
+ with col3:
417
+ if st.button("➕ Schedule Another"):
418
+ st.rerun()
419
+
420
+ with col4:
421
+ if st.button("📊 View Analytics"):
422
+ st.session_state.current_tab = "📊 Demo Analytics"
423
+ st.rerun()
424
+
425
+ except Exception as e:
426
+ st.error(f"Error displaying demo summary: {e}")
427
+
428
+
429
+ def show_demo_calendar_tab(db):
430
+ """Show demo calendar and upcoming demos"""
431
+ st.subheader("📋 Demo Calendar & Schedule")
432
+
433
+ try:
434
+ # Date range filter
435
+ col1, col2 = st.columns(2)
436
+ with col1:
437
+ start_date = st.date_input("Start Date", datetime.now().date())
438
+ with col2:
439
+ end_date = st.date_input("End Date", datetime.now().date() + timedelta(days=30))
440
+
441
+ # Status filter
442
+ status_filter = st.multiselect(
443
+ "Filter by Status",
444
+ ["Scheduled", "Completed", "Cancelled", "Converted", "Not Converted"],
445
+ default=["Scheduled", "Completed"],
446
+ )
447
+
448
+ # Get demos data
449
+ demos_data = get_demos_data(db, start_date, end_date, status_filter)
450
+
451
+ if not demos_data.empty:
452
+ # Convert demo_date to datetime for comparison
453
+ demos_data["demo_date"] = pd.to_datetime(demos_data["demo_date"]).dt.date
454
+
455
+ st.write(f"**📅 Showing {len(demos_data)} demos**")
456
+
457
+ # Upcoming demos (next 7 days)
458
+ upcoming_demos = demos_data[
459
+ (demos_data["demo_date"] <= datetime.now().date() + timedelta(days=7))
460
+ & (demos_data["conversion_status"] == "Scheduled")
461
+ ]
462
+
463
+ if not upcoming_demos.empty:
464
+ st.subheader("🚀 Upcoming Demos (Next 7 Days)")
465
+ display_upcoming = upcoming_demos[
466
+ [
467
+ "demo_date",
468
+ "customer_name",
469
+ "village",
470
+ "product_name",
471
+ "distributor_name",
472
+ ]
473
+ ].copy()
474
+ display_upcoming.columns = [
475
+ "Date",
476
+ "Customer",
477
+ "Village",
478
+ "Product",
479
+ "Distributor",
480
+ ]
481
+ st.dataframe(display_upcoming, use_container_width=True)
482
+
483
+ # All demos in date range
484
+ st.subheader("📋 All Demos")
485
+ display_all = demos_data[
486
+ [
487
+ "demo_date",
488
+ "customer_name",
489
+ "village",
490
+ "product_name",
491
+ "conversion_status",
492
+ "distributor_name",
493
+ ]
494
+ ].copy()
495
+ display_all.columns = [
496
+ "Date",
497
+ "Customer",
498
+ "Village",
499
+ "Product",
500
+ "Status",
501
+ "Distributor",
502
+ ]
503
+ display_all = display_all.sort_values("Date", ascending=False)
504
+ st.dataframe(display_all, use_container_width=True)
505
+
506
+ # Demo statistics
507
+ st.subheader("📊 Demo Statistics")
508
+ col1, col2, col3, col4 = st.columns(4)
509
+
510
+ with col1:
511
+ total_demos = len(demos_data)
512
+ st.metric("Total Demos", total_demos)
513
+
514
+ with col2:
515
+ scheduled = len(
516
+ demos_data[demos_data["conversion_status"] == "Scheduled"]
517
+ )
518
+ st.metric("Scheduled", scheduled)
519
+
520
+ with col3:
521
+ completed = len(
522
+ demos_data[demos_data["conversion_status"] == "Completed"]
523
+ )
524
+ st.metric("Completed", completed)
525
+
526
+ with col4:
527
+ converted = len(
528
+ demos_data[demos_data["conversion_status"] == "Converted"]
529
+ )
530
+ st.metric("Converted", converted)
531
+
532
+ else:
533
+ st.info("No demos found for the selected criteria.")
534
+
535
+ except Exception as e:
536
+ st.error(f"Error loading demo calendar: {e}")
537
+ import traceback
538
+ st.code(traceback.format_exc())
539
+
540
+
541
+ def get_demos_data(db, start_date, end_date, status_filter):
542
+ """Get demos data with filters"""
543
+ try:
544
+ query = """
545
+ SELECT d.*, c.name as customer_name, c.village, c.mobile,
546
+ p.product_name, dist.name as distributor_name
547
+ FROM demos d
548
+ LEFT JOIN customers c ON d.customer_id = c.customer_id
549
+ LEFT JOIN products p ON d.product_id = p.product_id
550
+ LEFT JOIN distributors dist ON d.distributor_id = dist.distributor_id
551
+ WHERE d.demo_date BETWEEN ? AND ?
552
+ """
553
+
554
+ params = [start_date, end_date]
555
+
556
+ if status_filter:
557
+ placeholders = ",".join(["?" for _ in status_filter])
558
+ query += f" AND d.conversion_status IN ({placeholders})"
559
+ params.extend(status_filter)
560
+
561
+ query += " ORDER BY d.demo_date, d.demo_time"
562
+
563
+ return db.get_dataframe("demos", query, params=params)
564
+
565
+ except Exception as e:
566
+ st.error(f"Error getting demos data: {e}")
567
+ return pd.DataFrame()
568
+
569
+
570
+ def show_demo_analytics_tab(db):
571
+ """Show demo analytics and conversion rates"""
572
+ st.subheader("📊 Demo Analytics")
573
+
574
+ try:
575
+ # Get demo conversion data
576
+ demos_data = db.get_dataframe(
577
+ "demos",
578
+ """
579
+ SELECT d.*, c.name as customer_name, c.village,
580
+ p.product_name, dist.name as distributor_name
581
+ FROM demos d
582
+ LEFT JOIN customers c ON d.customer_id = c.customer_id
583
+ LEFT JOIN products p ON d.product_id = p.product_id
584
+ LEFT JOIN distributors dist ON d.distributor_id = dist.distributor_id
585
+ ORDER BY d.demo_date DESC
586
+ """,
587
+ )
588
+
589
+ if not demos_data.empty:
590
+ # Convert demo_date to datetime for proper handling
591
+ demos_data["demo_date"] = pd.to_datetime(demos_data["demo_date"])
592
+
593
+ # Conversion statistics
594
+ st.subheader("🎯 Conversion Analytics")
595
+
596
+ col1, col2, col3, col4 = st.columns(4)
597
+
598
+ with col1:
599
+ total_demos = len(demos_data)
600
+ st.metric("Total Demos", total_demos)
601
+
602
+ with col2:
603
+ converted = len(
604
+ demos_data[demos_data["conversion_status"] == "Converted"]
605
+ )
606
+ st.metric("Converted", converted)
607
+
608
+ with col3:
609
+ not_converted = len(
610
+ demos_data[demos_data["conversion_status"] == "Not Converted"]
611
+ )
612
+ st.metric("Not Converted", not_converted)
613
+
614
+ with col4:
615
+ conversion_rate = (
616
+ (converted / total_demos * 100) if total_demos > 0 else 0
617
+ )
618
+ st.metric("Conversion Rate", f"{conversion_rate:.1f}%")
619
+
620
+ # Product-wise conversion
621
+ st.subheader("📦 Product Performance")
622
+ if "product_name" in demos_data.columns:
623
+ product_stats = (
624
+ demos_data.groupby("product_name")
625
+ .agg(
626
+ {
627
+ "demo_id": "count",
628
+ "conversion_status": lambda x: (x == "Converted").sum(),
629
+ }
630
+ )
631
+ .reset_index()
632
+ )
633
+ product_stats.columns = ["Product", "Total Demos", "Converted"]
634
+ product_stats["Conversion Rate"] = (
635
+ product_stats["Converted"] / product_stats["Total Demos"] * 100
636
+ ).round(1)
637
+ product_stats = product_stats.sort_values(
638
+ "Total Demos", ascending=False
639
+ )
640
+
641
+ st.dataframe(product_stats, use_container_width=True)
642
+
643
+ # Monthly trend
644
+ st.subheader("📈 Monthly Demo Trend")
645
+ try:
646
+ demos_data["demo_date"] = pd.to_datetime(demos_data["demo_date"])
647
+ monthly_trend = demos_data.groupby(
648
+ demos_data["demo_date"].dt.to_period("M")
649
+ ).size()
650
+ monthly_trend.index = monthly_trend.index.astype(str)
651
+
652
+ if not monthly_trend.empty:
653
+ fig = px.line(
654
+ x=monthly_trend.index,
655
+ y=monthly_trend.values,
656
+ title="Monthly Demo Trend",
657
+ labels={"x": "Month", "y": "Number of Demos"},
658
+ )
659
+ st.plotly_chart(fig, use_container_width=True)
660
+ except:
661
+ st.info("Could not generate monthly trend chart")
662
+
663
+ else:
664
+ st.info("No demo data available for analytics.")
665
+
666
+ except Exception as e:
667
+ st.error(f"Error loading demo analytics: {e}")
668
+
669
+
670
+ def show_follow_ups_tab(db, whatsapp_manager):
671
+ """Show demo follow-ups and conversion tracking"""
672
+ st.subheader("🔄 Demo Follow-ups")
673
+
674
+ try:
675
+ # Get demos needing follow-up
676
+ follow_up_data = db.get_dataframe(
677
+ "demos",
678
+ """
679
+ SELECT d.*, c.name as customer_name, c.mobile, c.village,
680
+ p.product_name, dist.name as distributor_name
681
+ FROM demos d
682
+ LEFT JOIN customers c ON d.customer_id = c.customer_id
683
+ LEFT JOIN products p ON d.product_id = p.product_id
684
+ LEFT JOIN distributors dist ON d.distributor_id = dist.distributor_id
685
+ WHERE d.follow_up_date <= date('now', '+7 days')
686
+ AND d.conversion_status IN ('Completed', 'Not Converted')
687
+ ORDER BY d.follow_up_date ASC
688
+ """,
689
+ )
690
+
691
+ if not follow_up_data.empty:
692
+ # Convert dates to datetime for comparison
693
+ follow_up_data["follow_up_date"] = pd.to_datetime(
694
+ follow_up_data["follow_up_date"]
695
+ ).dt.date
696
+
697
+ # Overdue follow-ups
698
+ overdue = follow_up_data[
699
+ follow_up_data["follow_up_date"] < datetime.now().date()
700
+ ]
701
+ if not overdue.empty:
702
+ st.warning(f"🚨 {len(overdue)} Overdue Follow-ups!")
703
+ display_overdue = overdue[
704
+ [
705
+ "follow_up_date",
706
+ "customer_name",
707
+ "village",
708
+ "product_name",
709
+ "conversion_status",
710
+ ]
711
+ ].copy()
712
+ display_overdue.columns = [
713
+ "Due Date",
714
+ "Customer",
715
+ "Village",
716
+ "Product",
717
+ "Status",
718
+ ]
719
+ st.dataframe(display_overdue, use_container_width=True)
720
+
721
+ # Upcoming follow-ups
722
+ upcoming = follow_up_data[
723
+ follow_up_data["follow_up_date"] >= datetime.now().date()
724
+ ]
725
+ if not upcoming.empty:
726
+ st.subheader("📅 Upcoming Follow-ups")
727
+ display_upcoming = upcoming[
728
+ [
729
+ "follow_up_date",
730
+ "customer_name",
731
+ "village",
732
+ "product_name",
733
+ "conversion_status",
734
+ ]
735
+ ].copy()
736
+ display_upcoming.columns = [
737
+ "Due Date",
738
+ "Customer",
739
+ "Village",
740
+ "Product",
741
+ "Status",
742
+ ]
743
+ st.dataframe(display_upcoming, use_container_width=True)
744
+
745
+ # Follow-up actions
746
+ st.subheader("🔄 Follow-up Actions")
747
+ selected_demo = st.selectbox(
748
+ "Select Demo for Follow-up",
749
+ options=[
750
+ f"{row['customer_name']} - {row['product_name']} ({row['follow_up_date']})"
751
+ for _, row in follow_up_data.iterrows()
752
+ ],
753
+ )
754
+
755
+ if selected_demo:
756
+ demo_index = [
757
+ f"{row['customer_name']} - {row['product_name']} ({row['follow_up_date']})"
758
+ for _, row in follow_up_data.iterrows()
759
+ ].index(selected_demo)
760
+ selected_demo_data = follow_up_data.iloc[demo_index]
761
+
762
+ col1, col2 = st.columns(2)
763
+
764
+ with col1:
765
+ new_status = st.selectbox(
766
+ "Update Conversion Status",
767
+ ["Converted", "Not Converted", "Follow-up Required", "Lost"],
768
+ )
769
+
770
+ if st.button("🔄 Update Status"):
771
+ update_demo_status(
772
+ db, selected_demo_data["demo_id"], new_status
773
+ )
774
+ st.success("✅ Status updated successfully!")
775
+ st.rerun()
776
+
777
+ with col2:
778
+ if whatsapp_manager and st.button("📱 Send Follow-up Message"):
779
+ send_follow_up_message(whatsapp_manager, selected_demo_data)
780
+ st.success("✅ Follow-up message sent!")
781
+
782
+ else:
783
+ st.success("🎉 No pending follow-ups! All demos are up to date.")
784
+
785
+ except Exception as e:
786
+ st.error(f"Error loading follow-ups: {e}")
787
+
788
+
789
+ def update_demo_status(db, demo_id, new_status):
790
+ """Update demo conversion status"""
791
+ try:
792
+ db.execute_query(
793
+ """
794
+ UPDATE demos SET conversion_status = ?, updated_date = CURRENT_TIMESTAMP
795
+ WHERE demo_id = ?
796
+ """,
797
+ (new_status, demo_id),
798
+ log_action=False,
799
+ )
800
+ except Exception as e:
801
+ st.error(f"Error updating demo status: {e}")
802
+
803
+
804
+ def send_follow_up_message(whatsapp_manager, demo_data):
805
+ """Send follow-up message for demo"""
806
+ try:
807
+ if demo_data.get("mobile"):
808
+ message = f"""Hello {demo_data["customer_name"]}! 👋
809
+
810
+ Following up on your {demo_data["product_name"]} demo from {demo_data["demo_date"]}.
811
+
812
+ We'd love to hear about your experience and answer any questions you may have.
813
+
814
+ Would you be interested in placing an order or scheduling another demo?
815
+
816
+ Best regards,
817
+ Sales Team"""
818
+
819
+ success = whatsapp_manager.send_message(demo_data["mobile"], message)
820
+ return success
821
+ return False
822
+ except Exception as e:
823
+ st.error(f"Error sending follow-up message: {e}")
824
+ return False
pages/distributors.py ADDED
@@ -0,0 +1,971 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # pages/distributors.py
2
+ import streamlit as st
3
+ import pandas as pd
4
+ import plotly.express as px
5
+ import plotly.graph_objects as go
6
+ from datetime import datetime, timedelta
7
+ import numpy as np
8
+ # pages/distributors.py
9
+ import streamlit as st
10
+ import pandas as pd
11
+ import plotly.express as px
12
+ import plotly.graph_objects as go
13
+ from datetime import datetime, timedelta
14
+ import numpy as np
15
+
16
+ def show_distributors_page(db, whatsapp_manager=None):
17
+ """Show intelligent distributors network optimization hub"""
18
+ st.title("🤝 Distributor Network Intelligence")
19
+
20
+ if not db:
21
+ st.error("Database not available. Please check initialization.")
22
+ return
23
+
24
+ # Tabs for different distributor functions
25
+ tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs(["🏆 Performance Dashboard", "➕ Add New Distributor",
26
+ "🗺️ Territory Analysis", "📈 Growth Opportunities",
27
+ "👥 Team Management", "🔍 Distributor Directory"])
28
+
29
+ with tab1:
30
+ show_performance_dashboard_tab(db)
31
+
32
+ with tab2:
33
+ show_add_distributor_tab(db, whatsapp_manager)
34
+
35
+ with tab3:
36
+ show_territory_analysis_tab(db)
37
+
38
+ with tab4:
39
+ show_growth_opportunities_tab(db)
40
+
41
+ with tab5:
42
+ show_team_management_tab(db, whatsapp_manager)
43
+
44
+ with tab6:
45
+ show_distributor_directory_tab(db)
46
+
47
+ def show_add_distributor_tab(db, whatsapp_manager):
48
+ """Show form to add new distributors with comprehensive data collection"""
49
+ st.subheader("➕ Add New Distributor")
50
+
51
+ with st.form("add_distributor_form", clear_on_submit=True):
52
+ st.markdown("### 📋 Basic Information")
53
+
54
+ col1, col2 = st.columns(2)
55
+
56
+ with col1:
57
+ distributor_name = st.text_input("Distributor Name*", placeholder="Enter distributor name")
58
+ village = st.text_input("Village*", placeholder="Enter village name")
59
+ taluka = st.text_input("Taluka*", placeholder="Enter taluka name")
60
+ district = st.text_input("District", placeholder="Enter district name")
61
+
62
+ with col2:
63
+ mantri_name = st.text_input("Mantri Name*", placeholder="Enter mantri name")
64
+ mantri_mobile = st.text_input("Mantri Mobile*", placeholder="Enter 10-digit mobile number")
65
+ # Remove status for now since your function doesn't have it
66
+ # status = st.selectbox("Status", ["Active", "Inactive", "Prospective"], index=0)
67
+
68
+ st.markdown("### 📊 Network Information")
69
+
70
+ col1, col2 = st.columns(2)
71
+
72
+ with col1:
73
+ sabhasad_count = st.number_input("Current Sabhasad Count", min_value=0, value=0)
74
+ contact_in_group = st.number_input("Contacts in WhatsApp Group", min_value=0, value=0)
75
+
76
+ with col2:
77
+ potential_sabhasad = st.number_input("Potential Sabhasad (6 months)", min_value=0, value=0)
78
+ market_coverage = st.slider("Market Coverage (%)", 0, 100, 50)
79
+
80
+ # Quick duplicate check
81
+ if distributor_name and village and taluka:
82
+ if db.distributor_exists(distributor_name, village, taluka):
83
+ st.warning("⚠️ A distributor with this name already exists in this location!")
84
+
85
+ # Submit button
86
+ submitted = st.form_submit_button("🚀 Add Distributor to Network", type="primary")
87
+
88
+ if submitted:
89
+ # Validation
90
+ errors = []
91
+ if not distributor_name:
92
+ errors.append("Distributor name is required")
93
+ if not village:
94
+ errors.append("Village is required")
95
+ if not taluka:
96
+ errors.append("Taluka is required")
97
+ if not mantri_name:
98
+ errors.append("Mantri name is required")
99
+ if not mantri_mobile or len(mantri_mobile) < 10:
100
+ errors.append("Valid mobile number is required (10 digits)")
101
+
102
+ if errors:
103
+ for error in errors:
104
+ st.error(f"❌ {error}")
105
+ else:
106
+ try:
107
+ # Add distributor to database - WITHOUT status parameter
108
+ distributor_id = db.add_distributor(
109
+ name=distributor_name,
110
+ village=village,
111
+ taluka=taluka,
112
+ district=district,
113
+ mantri_name=mantri_name,
114
+ mantri_mobile=mantri_mobile,
115
+ sabhasad_count=sabhasad_count,
116
+ contact_in_group=contact_in_group
117
+ # status=status # Remove this line since your function doesn't have it
118
+ )
119
+
120
+ if distributor_id and distributor_id > 0:
121
+ st.success(f"✅ Distributor '{distributor_name}' added successfully!")
122
+
123
+ # Store additional metrics
124
+ save_distributor_metrics(db, distributor_id, {
125
+ 'potential_sabhasad': potential_sabhasad,
126
+ 'market_coverage': market_coverage,
127
+ 'notes': "Added via distributor form"
128
+ })
129
+
130
+ # Show success summary
131
+ show_distributor_summary(db, distributor_id)
132
+
133
+ # Send welcome message
134
+ if whatsapp_manager and mantri_mobile:
135
+ send_welcome_message(whatsapp_manager, mantri_mobile, distributor_name)
136
+
137
+ else:
138
+ st.error("❌ Failed to add distributor. Please try again.")
139
+
140
+ except Exception as e:
141
+ st.error(f"❌ Error adding distributor: {e}")
142
+
143
+ def calculate_potential_score(sabhasad_count, contact_in_group, potential_sabhasad,
144
+ market_coverage, leadership_quality, community_influence,
145
+ business_experience, has_vehicle, digital_literacy):
146
+ """Calculate distributor potential score (0-100)"""
147
+ score = 0
148
+
149
+ # Network factors (40%)
150
+ score += min(sabhasad_count * 2, 20) # Max 20 points for current sabhasad
151
+ score += min(contact_in_group * 0.2, 10) # Max 10 points for contacts
152
+ score += min(potential_sabhasad * 1.5, 10) # Max 10 points for potential
153
+
154
+ # Market factors (20%)
155
+ score += market_coverage * 0.2 # 20 points for coverage
156
+
157
+ # Personal factors (25%)
158
+ leadership_scores = {"Low": 0, "Medium": 5, "High": 8, "Very High": 10}
159
+ score += leadership_scores.get(leadership_quality, 0)
160
+
161
+ influence_scores = {"Low": 0, "Medium": 5, "High": 8, "Very High": 10}
162
+ score += influence_scores.get(community_influence, 0)
163
+
164
+ experience_scores = {"None": 0, "1-2 years": 2, "3-5 years": 3, "5+ years": 5}
165
+ score += experience_scores.get(business_experience, 0)
166
+
167
+ # Infrastructure factors (15%)
168
+ if has_vehicle:
169
+ score += 5
170
+ digital_scores = {"Basic": 2, "Intermediate": 4, "Advanced": 6}
171
+ score += digital_scores.get(digital_literacy, 0)
172
+
173
+ return min(score, 100)
174
+
175
+ def save_distributor_metrics(db, distributor_id, metrics):
176
+ """Save additional distributor metrics"""
177
+ try:
178
+ # Create metrics table if not exists
179
+ db.execute_query('''
180
+ CREATE TABLE IF NOT EXISTS distributor_metrics (
181
+ metric_id INTEGER PRIMARY KEY AUTOINCREMENT,
182
+ distributor_id INTEGER,
183
+ potential_sabhasad INTEGER,
184
+ market_coverage INTEGER,
185
+ monthly_target REAL,
186
+ current_business_value REAL,
187
+ has_vehicle BOOLEAN,
188
+ vehicle_type TEXT,
189
+ storage_capacity TEXT,
190
+ whatsapp_active BOOLEAN,
191
+ digital_literacy TEXT,
192
+ uses_app BOOLEAN,
193
+ business_experience TEXT,
194
+ sales_background BOOLEAN,
195
+ leadership_quality TEXT,
196
+ community_influence TEXT,
197
+ known_in_village BOOLEAN,
198
+ reference_source TEXT,
199
+ potential_score REAL,
200
+ notes TEXT,
201
+ created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
202
+ FOREIGN KEY (distributor_id) REFERENCES distributors (distributor_id) ON DELETE CASCADE
203
+ )
204
+ ''', log_action=False)
205
+
206
+ # Insert metrics
207
+ db.execute_query('''
208
+ INSERT INTO distributor_metrics (
209
+ distributor_id, potential_sabhasad, market_coverage, monthly_target,
210
+ current_business_value, has_vehicle, vehicle_type, storage_capacity,
211
+ whatsapp_active, digital_literacy, uses_app, business_experience,
212
+ sales_background, leadership_quality, community_influence, known_in_village,
213
+ reference_source, potential_score, notes
214
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
215
+ ''', (
216
+ distributor_id, metrics['potential_sabhasad'], metrics['market_coverage'],
217
+ metrics['monthly_target'], metrics['current_business_value'],
218
+ metrics['has_vehicle'], metrics['vehicle_type'], metrics['storage_capacity'],
219
+ metrics['whatsapp_active'], metrics['digital_literacy'], metrics['uses_app'],
220
+ metrics['business_experience'], metrics['sales_background'],
221
+ metrics['leadership_quality'], metrics['community_influence'],
222
+ metrics['known_in_village'], metrics['reference_source'],
223
+ metrics['potential_score'], metrics['notes']
224
+ ), log_action=False)
225
+
226
+ except Exception as e:
227
+ st.warning(f"Could not save additional metrics: {e}")
228
+
229
+ def show_distributor_summary(name, village, taluka, mantri_name, sabhasad_count,
230
+ contact_in_group, potential_sabhasad, potential_score, monthly_target):
231
+ """Show summary of newly added distributor"""
232
+ st.markdown("## 🎉 Distributor Added Successfully!")
233
+
234
+ col1, col2 = st.columns(2)
235
+
236
+ with col1:
237
+ st.subheader("👤 Basic Information")
238
+ st.write(f"**Name:** {name}")
239
+ st.write(f"**Village:** {village}")
240
+ st.write(f"**Taluka:** {taluka}")
241
+ st.write(f"**Mantri:** {mantri_name}")
242
+
243
+ with col2:
244
+ st.subheader("📊 Network Metrics")
245
+ st.write(f"**Current Sabhasad:** {sabhasad_count}")
246
+ st.write(f"**WhatsApp Contacts:** {contact_in_group}")
247
+ st.write(f"**Potential Sabhasad:** {potential_sabhasad}")
248
+ st.write(f"**Monthly Target:** ₹{monthly_target:,.0f}")
249
+
250
+ st.subheader("🎯 Potential Assessment")
251
+
252
+ # Potential score visualization
253
+ col1, col2, col3 = st.columns([1, 2, 1])
254
+
255
+ with col2:
256
+ # Create gauge chart for potential score
257
+ fig = go.Figure(go.Indicator(
258
+ mode = "gauge+number+delta",
259
+ value = potential_score,
260
+ domain = {'x': [0, 1], 'y': [0, 1]},
261
+ title = {'text': "Potential Score"},
262
+ delta = {'reference': 50},
263
+ gauge = {
264
+ 'axis': {'range': [None, 100]},
265
+ 'bar': {'color': "darkblue"},
266
+ 'steps': [
267
+ {'range': [0, 40], 'color': "lightgray"},
268
+ {'range': [40, 70], 'color': "gray"},
269
+ {'range': [70, 100], 'color': "lightblue"}
270
+ ],
271
+ 'threshold': {
272
+ 'line': {'color': "red", 'width': 4},
273
+ 'thickness': 0.75,
274
+ 'value': 90
275
+ }
276
+ }
277
+ ))
278
+
279
+ fig.update_layout(height=300)
280
+ st.plotly_chart(fig, use_container_width=True)
281
+
282
+ # Action recommendations based on score
283
+ st.subheader("💡 Recommended Actions")
284
+
285
+ if potential_score >= 80:
286
+ st.success("**🎯 High Potential Distributor**")
287
+ st.write("- Provide advanced training materials")
288
+ st.write("- Set ambitious growth targets")
289
+ st.write("- Consider for leadership role")
290
+ st.write("- Regular high-level engagement")
291
+
292
+ elif potential_score >= 60:
293
+ st.info("**📈 Good Potential Distributor**")
294
+ st.write("- Standard training program")
295
+ st.write("- Moderate growth targets")
296
+ st.write("- Regular follow-ups")
297
+ st.write("- Support with marketing materials")
298
+
299
+ elif potential_score >= 40:
300
+ st.warning("**🔄 Moderate Potential Distributor**")
301
+ st.write("- Basic training focus")
302
+ st.write("- Conservative targets")
303
+ st.write("- Close monitoring needed")
304
+ st.write("- Additional support required")
305
+
306
+ else:
307
+ st.error("**⚠️ Needs Development**")
308
+ st.write("- Intensive training program")
309
+ st.write("- Small, achievable targets")
310
+ st.write("- Frequent check-ins")
311
+ st.write("- Consider mentorship")
312
+
313
+ def send_welcome_message(whatsapp_manager, mobile, distributor_name):
314
+ """Send welcome message to new distributor"""
315
+ try:
316
+ message = f"""Welcome {distributor_name}! 🎉
317
+
318
+ Thank you for joining our distributor network!
319
+
320
+ We're excited to have you on board and look forward to working together to grow your business.
321
+
322
+ Our team will contact you shortly to discuss:
323
+ • Training schedule
324
+ • Product information
325
+ • Sales strategies
326
+ • Support systems
327
+
328
+ For any immediate queries, feel free to contact us.
329
+
330
+ Best regards,
331
+ Sales Team"""
332
+
333
+ success = whatsapp_manager.send_message(mobile, message)
334
+ if success:
335
+ st.success("📱 Welcome message sent to distributor!")
336
+ else:
337
+ st.warning("⚠️ Could not send welcome message")
338
+
339
+ except Exception as e:
340
+ st.warning(f"Could not send welcome message: {e}")
341
+
342
+ # Keep all your existing functions (show_performance_dashboard_tab, show_territory_analysis_tab, etc.)
343
+ # ... [rest of your existing functions remain unchanged]
344
+
345
+ def show_performance_dashboard_tab(db):
346
+ """Show distributor performance dashboard"""
347
+ st.subheader("🏆 Distributor Performance Dashboard")
348
+
349
+ try:
350
+ distributors_data = get_distributor_analytics_data(db)
351
+
352
+ if distributors_data.empty:
353
+ st.info("No distributor data available yet.")
354
+ return
355
+
356
+ except:
357
+ pass
358
+ def show_distributors_page(db, whatsapp_manager=None):
359
+ """Show intelligent distributors network optimization hub"""
360
+ st.title("🤝 Distributor Network Intelligence")
361
+
362
+ if not db:
363
+ st.error("Database not available. Please check initialization.")
364
+ return
365
+
366
+ # Tabs for different distributor functions
367
+ tab1, tab2, tab3, tab4, tab5 ,tab6= st.tabs(["🏆 Performance Dashboard", "🗺️ Territory Analysis",
368
+ "📈 Growth Opportunities", "👥 Team Management",
369
+ "🔍 Distributor Directory","➕Add new distributor"])
370
+
371
+ with tab1:
372
+ show_performance_dashboard_tab(db)
373
+
374
+ with tab2:
375
+ show_territory_analysis_tab(db)
376
+
377
+ with tab3:
378
+ show_growth_opportunities_tab(db)
379
+
380
+ with tab4:
381
+ show_team_management_tab(db, whatsapp_manager)
382
+
383
+ with tab5:
384
+ show_distributor_directory_tab(db)
385
+
386
+ with tab6:
387
+ show_add_distributor_tab(db)
388
+
389
+ def show_performance_dashboard_tab(db):
390
+ """Show distributor performance dashboard"""
391
+ st.subheader("🏆 Distributor Performance Dashboard")
392
+
393
+ try:
394
+ distributors_data = get_distributor_analytics_data(db)
395
+
396
+ if distributors_data.empty:
397
+ st.info("No distributor data available yet.")
398
+ return
399
+
400
+ # Key Performance Indicators
401
+ st.subheader("🎯 Key Performance Indicators")
402
+ col1, col2, col3, col4 = st.columns(4)
403
+
404
+ with col1:
405
+ total_distributors = len(distributors_data)
406
+ st.metric("Total Distributors", total_distributors)
407
+
408
+ with col2:
409
+ active_distributors = len(distributors_data[distributors_data['total_customers'] > 0])
410
+ st.metric("Active Distributors", active_distributors)
411
+
412
+ with col3:
413
+ avg_sabhasad_per_dist = distributors_data['sabhasad_count'].mean()
414
+ st.metric("Avg Sabhasad/Dist", f"{avg_sabhasad_per_dist:.1f}")
415
+
416
+ with col4:
417
+ total_network_size = distributors_data['sabhasad_count'].sum() + total_distributors
418
+ st.metric("Total Network Size", total_network_size)
419
+
420
+ # Performance Tiers
421
+ st.subheader("📊 Performance Tiers")
422
+
423
+ # Define performance tiers based on sabhasad count
424
+ distributors_data['performance_tier'] = distributors_data['sabhasad_count'].apply(
425
+ lambda x: 'Platinum' if x >= 20 else 'Gold' if x >= 10 else 'Silver' if x >= 5 else 'Bronze'
426
+ )
427
+
428
+ tier_stats = distributors_data['performance_tier'].value_counts()
429
+
430
+ col1, col2 = st.columns(2)
431
+
432
+ with col1:
433
+ fig = px.pie(values=tier_stats.values, names=tier_stats.index,
434
+ title='Distributor Performance Tier Distribution',
435
+ color=tier_stats.index,
436
+ color_discrete_map={'Platinum': '#FFD700', 'Gold': '#C0C0C0',
437
+ 'Silver': '#CD7F32', 'Bronze': '#8C7853'})
438
+ st.plotly_chart(fig, use_container_width=True)
439
+
440
+ with col2:
441
+ # Top performers
442
+ top_performers = distributors_data.nlargest(5, 'sabhasad_count')[
443
+ ['name', 'village', 'sabhasad_count', 'contact_in_group']
444
+ ]
445
+ top_performers.columns = ['Distributor', 'Village', 'Sabhasad', 'Contacts']
446
+
447
+ st.write("**🏅 Top 5 Performers**")
448
+ st.dataframe(top_performers, use_container_width=True)
449
+
450
+ # Geographic Performance Heatmap
451
+ st.subheader("🗺️ Geographic Performance Distribution")
452
+
453
+ village_performance = distributors_data.groupby('village').agg({
454
+ 'distributor_id': 'count',
455
+ 'sabhasad_count': 'sum',
456
+ 'contact_in_group': 'sum'
457
+ }).reset_index()
458
+ village_performance.columns = ['Village', 'Distributors', 'Total Sabhasad', 'Total Contacts']
459
+ village_performance = village_performance.sort_values('Total Sabhasad', ascending=False)
460
+
461
+ if not village_performance.empty:
462
+ fig = px.bar(village_performance.head(10), x='Village', y='Total Sabhasad',
463
+ title='Top 10 Villages by Sabhasad Network Size',
464
+ color='Total Sabhasad',
465
+ labels={'Total Sabhasad': 'Sabhasad Count'})
466
+ st.plotly_chart(fig, use_container_width=True)
467
+
468
+ # Performance Trends (if we had date data)
469
+ st.subheader("📈 Network Growth Potential")
470
+
471
+ # Calculate network density score
472
+ distributors_data['network_score'] = (
473
+ distributors_data['sabhasad_count'] * 0.6 +
474
+ distributors_data['contact_in_group'] * 0.4
475
+ )
476
+
477
+ # Identify high-potential distributors
478
+ high_potential = distributors_data[
479
+ (distributors_data['sabhasad_count'] < 10) &
480
+ (distributors_data['contact_in_group'] > 20)
481
+ ]
482
+
483
+ if not high_potential.empty:
484
+ st.write(f"**💎 {len(high_potential)} High-Potential Distributors Identified**")
485
+ st.write("These distributors have good contact base but low sabhasad conversion")
486
+ st.dataframe(high_potential[['name', 'village', 'sabhasad_count', 'contact_in_group']],
487
+ use_container_width=True)
488
+
489
+ except Exception as e:
490
+ st.error(f"Error loading performance dashboard: {e}")
491
+
492
+ def show_territory_analysis_tab(db):
493
+ """Show territory coverage and gap analysis"""
494
+ st.subheader("🗺️ Territory Coverage Analysis")
495
+
496
+ try:
497
+ distributors_data = get_distributor_analytics_data(db)
498
+ customers_data = get_customer_analytics_data(db)
499
+
500
+ if distributors_data.empty or customers_data.empty:
501
+ st.info("Insufficient data for territory analysis.")
502
+ return
503
+
504
+ # Territory Coverage Analysis
505
+ st.subheader("📍 Coverage Gap Analysis")
506
+
507
+ # Get all villages with distributors vs all villages with customers
508
+ distributor_villages = set(distributors_data['village'].dropna().unique())
509
+ customer_villages = set(customers_data['village'].dropna().unique())
510
+
511
+ # Coverage analysis
512
+ covered_villages = distributor_villages.intersection(customer_villages)
513
+ uncovered_villages = customer_villages - distributor_villages
514
+ distributor_only_villages = distributor_villages - customer_villages
515
+
516
+ col1, col2, col3 = st.columns(3)
517
+
518
+ with col1:
519
+ st.metric("Covered Villages", len(covered_villages))
520
+
521
+ with col2:
522
+ st.metric("Uncovered Villages", len(uncovered_villages))
523
+
524
+ with col3:
525
+ st.metric("Distributor-Only Villages", len(distributor_only_villages))
526
+
527
+ # Coverage visualization
528
+ col1, col2 = st.columns(2)
529
+
530
+ with col1:
531
+ coverage_data = {
532
+ 'Category': ['Covered', 'Uncovered', 'Distributor Only'],
533
+ 'Count': [len(covered_villages), len(uncovered_villages), len(distributor_only_villages)]
534
+ }
535
+ coverage_df = pd.DataFrame(coverage_data)
536
+
537
+ fig = px.pie(coverage_df, values='Count', names='Category',
538
+ title='Village Coverage Status',
539
+ color='Category',
540
+ color_discrete_map={'Covered': '#00FF00', 'Uncovered': '#FF0000',
541
+ 'Distributor Only': '#FFFF00'})
542
+ st.plotly_chart(fig, use_container_width=True)
543
+
544
+ with col2:
545
+ # Customer density in uncovered areas
546
+ if uncovered_villages:
547
+ uncovered_customers = customers_data[customers_data['village'].isin(uncovered_villages)]
548
+ village_customer_count = uncovered_customers['village'].value_counts().head(10)
549
+
550
+ if not village_customer_count.empty:
551
+ fig = px.bar(x=village_customer_count.index, y=village_customer_count.values,
552
+ title='Top Uncovered Villages by Customer Count',
553
+ labels={'x': 'Village', 'y': 'Customer Count'})
554
+ st.plotly_chart(fig, use_container_width=True)
555
+
556
+ # Strategic Expansion Recommendations
557
+ st.subheader("🎯 Strategic Expansion Recommendations")
558
+
559
+ if uncovered_villages:
560
+ # Prioritize villages with most customers
561
+ expansion_priority = customers_data[customers_data['village'].isin(uncovered_villages)]
562
+ priority_villages = expansion_priority.groupby('village').agg({
563
+ 'customer_id': 'count',
564
+ 'total_spent': 'sum'
565
+ }).reset_index()
566
+ priority_villages = priority_villages.sort_values('customer_id', ascending=False)
567
+
568
+ st.write("**🚀 High-Priority Expansion Targets**")
569
+ st.dataframe(priority_villages.head(10), use_container_width=True)
570
+
571
+ # Expansion strategy
572
+ st.write("**📋 Recommended Expansion Strategy**")
573
+
574
+ high_priority = priority_villages[priority_villages['customer_id'] >= 10]
575
+ medium_priority = priority_villages[(priority_villages['customer_id'] >= 5) &
576
+ (priority_villages['customer_id'] < 10)]
577
+
578
+ if not high_priority.empty:
579
+ st.success(f"**Immediate Action Needed:** {len(high_priority)} villages with 10+ customers need distributor coverage")
580
+
581
+ if not medium_priority.empty:
582
+ st.warning(f"**Plan Expansion:** {len(medium_priority)} villages with 5-9 customers ready for coverage")
583
+
584
+ # Territory Optimization
585
+ st.subheader("⚡ Territory Optimization")
586
+
587
+ # Identify overcrowded territories
588
+ village_distributor_count = distributors_data['village'].value_counts()
589
+ overcrowded_villages = village_distributor_count[village_distributor_count > 2]
590
+
591
+ if not overcrowded_villages.empty:
592
+ st.write("**🏙️ Overcrowded Territories**")
593
+ st.write("Consider redistributing some distributors from these villages:")
594
+ for village, count in overcrowded_villages.items():
595
+ st.write(f"- {village}: {count} distributors")
596
+
597
+ except Exception as e:
598
+ st.error(f"Error in territory analysis: {e}")
599
+
600
+ def show_growth_opportunities_tab(db):
601
+ """Show growth opportunities and network expansion"""
602
+ st.subheader("📈 Network Growth Opportunities")
603
+
604
+ try:
605
+ distributors_data = get_distributor_analytics_data(db)
606
+
607
+ if distributors_data.empty:
608
+ st.info("No distributor data available for growth analysis.")
609
+ return
610
+
611
+ # Growth Levers Analysis
612
+ st.subheader("🎯 Growth Lever Analysis")
613
+
614
+ col1, col2, col3 = st.columns(3)
615
+
616
+ with col1:
617
+ # Sabhasad Conversion Opportunity
618
+ avg_conversion_rate = distributors_data['sabhasad_count'].sum() / distributors_data['contact_in_group'].sum() * 100
619
+ st.metric("Avg Contact to Sabhasad Rate", f"{avg_conversion_rate:.1f}%")
620
+
621
+ with col2:
622
+ # Underperforming distributors
623
+ underperformers = len(distributors_data[distributors_data['sabhasad_count'] < 3])
624
+ st.metric("Distributors Needing Support", underperformers)
625
+
626
+ with col3:
627
+ # Expansion potential
628
+ total_contacts = distributors_data['contact_in_group'].sum()
629
+ potential_sabhasad = total_contacts * 0.3 # Assuming 30% conversion potential
630
+ st.metric("Potential Sabhasad Growth", f"+{potential_sabhasad:.0f}")
631
+
632
+ # Growth Initiatives
633
+ st.subheader("🚀 Growth Initiatives")
634
+
635
+ initiative = st.selectbox("Select Growth Initiative",
636
+ ["Sabhasad Conversion Drive", "New Distributor Recruitment",
637
+ "Territory Expansion", "Performance Improvement Program"])
638
+
639
+ if initiative == "Sabhasad Conversion Drive":
640
+ show_sabhasad_conversion_plan(db, distributors_data)
641
+
642
+ elif initiative == "New Distributor Recruitment":
643
+ show_recruitment_plan(db, distributors_data)
644
+
645
+ elif initiative == "Territory Expansion":
646
+ show_territory_expansion_plan(db)
647
+
648
+ elif initiative == "Performance Improvement Program":
649
+ show_performance_improvement_plan(db, distributors_data)
650
+
651
+ # Progress Tracking
652
+ st.subheader("📊 Initiative Progress Tracking")
653
+
654
+ # Mock progress data - in real implementation, this would come from database
655
+ progress_data = {
656
+ 'Initiative': ['Sabhasad Drive', 'Recruitment', 'Territory Expansion', 'Training'],
657
+ 'Target': [50, 10, 5, 25],
658
+ 'Achieved': [35, 7, 3, 20],
659
+ 'Completion': [70, 70, 60, 80]
660
+ }
661
+ progress_df = pd.DataFrame(progress_data)
662
+
663
+ fig = px.bar(progress_df, x='Initiative', y='Completion',
664
+ title='Growth Initiative Progress',
665
+ labels={'Completion': 'Completion %'},
666
+ color='Completion')
667
+ st.plotly_chart(fig, use_container_width=True)
668
+
669
+ except Exception as e:
670
+ st.error(f"Error in growth opportunities analysis: {e}")
671
+
672
+ def show_sabhasad_conversion_plan(db, distributors_data):
673
+ """Show sabhasad conversion growth plan"""
674
+ st.write("### 📈 Sabhasad Conversion Drive")
675
+
676
+ # Identify best candidates for conversion
677
+ conversion_candidates = distributors_data[
678
+ (distributors_data['contact_in_group'] > distributors_data['sabhasad_count'] * 2) &
679
+ (distributors_data['contact_in_group'] >= 10)
680
+ ].sort_values('contact_in_group', ascending=False)
681
+
682
+ if not conversion_candidates.empty:
683
+ st.write(f"**🎯 {len(conversion_candidates)} Distributors with High Conversion Potential**")
684
+
685
+ for _, dist in conversion_candidates.head(5).iterrows():
686
+ conversion_potential = dist['contact_in_group'] - dist['sabhasad_count']
687
+ st.write(f"- **{dist['name']}** ({dist['village']}): {dist['sabhasad_count']} sabhasad, "
688
+ f"{dist['contact_in_group']} contacts → **+{conversion_potential} potential**")
689
+
690
+ # Action plan
691
+ st.write("**📋 Action Plan**")
692
+ st.write("1. Conduct conversion training sessions")
693
+ st.write("2. Provide conversion scripts and materials")
694
+ st.write("3. Set weekly conversion targets")
695
+ st.write("4. Implement incentive program for conversions")
696
+ else:
697
+ st.info("No high-potential conversion candidates identified.")
698
+
699
+ def show_recruitment_plan(db, distributors_data):
700
+ """Show new distributor recruitment plan"""
701
+ st.write("### 👥 New Distributor Recruitment")
702
+
703
+ # Analyze current distribution density
704
+ village_coverage = distributors_data['village'].value_counts()
705
+ low_coverage_villages = village_coverage[village_coverage == 1]
706
+
707
+ if not low_coverage_villages.empty:
708
+ st.write("**📍 Villages Needing Additional Distributors**")
709
+ for village in low_coverage_villages.index[:5]:
710
+ st.write(f"- {village}")
711
+
712
+ # Recruitment targets
713
+ st.write("**🎯 Recruitment Strategy**")
714
+ st.write("- Focus on high-customer-density uncovered villages")
715
+ st.write("- Target influential community members")
716
+ st.write("- Offer attractive onboarding incentives")
717
+ st.write("- Provide comprehensive training and support")
718
+
719
+ def show_territory_expansion_plan(db):
720
+ """Show territory expansion strategy"""
721
+ st.write("### 🗺️ Territory Expansion Plan")
722
+
723
+ # This would integrate with the territory analysis data
724
+ st.write("**🚀 Expansion Priority Areas**")
725
+ st.write("1. High customer density uncovered villages")
726
+ st.write("2. Adjacent territories to high-performing distributors")
727
+ st.write("3. Villages with existing brand awareness")
728
+ st.write("4. Areas with competitor weakness")
729
+
730
+ def show_performance_improvement_plan(db, distributors_data):
731
+ """Show performance improvement program"""
732
+ st.write("### 📊 Performance Improvement Program")
733
+
734
+ # Identify underperformers
735
+ underperformers = distributors_data[
736
+ (distributors_data['sabhasad_count'] < 5) &
737
+ (distributors_data['status'] == 'Active')
738
+ ]
739
+
740
+ if not underperformers.empty:
741
+ st.write(f"**🔧 {len(underperformers)} Distributors Needing Performance Support**")
742
+
743
+ for _, dist in underperformers.head(5).iterrows():
744
+ st.write(f"- **{dist['name']}** ({dist['village']}): {dist['sabhasad_count']} sabhasad")
745
+
746
+ # Support plan
747
+ st.write("**🛠️ Support Initiatives**")
748
+ st.write("1. One-on-one coaching sessions")
749
+ st.write("2. Performance benchmarking")
750
+ st.write("3. Additional training resources")
751
+ st.write("4. Peer mentoring program")
752
+
753
+ def show_team_management_tab(db, whatsapp_manager):
754
+ """Show team communication and management"""
755
+ st.subheader("👥 Team Management & Communication")
756
+
757
+ try:
758
+ distributors_data = get_distributor_analytics_data(db)
759
+
760
+ if distributors_data.empty:
761
+ st.info("No distributor data available for team management.")
762
+ return
763
+
764
+ # Communication Center
765
+ st.subheader("📞 Communication Center")
766
+
767
+ col1, col2 = st.columns(2)
768
+
769
+ with col1:
770
+ communication_type = st.selectbox("Communication Type",
771
+ ["Performance Update", "Training Announcement",
772
+ "Incentive Program", "Urgent Meeting", "Custom Message"])
773
+
774
+ with col2:
775
+ target_group = st.selectbox("Target Group",
776
+ ["All Distributors", "High Performers", "Underperformers",
777
+ "Specific Village", "Performance Tier"])
778
+
779
+ # Message templates
780
+ message_templates = {
781
+ "Performance Update": "Hello {name}! Your current performance: {sabhasad_count} sabhasad. Keep up the great work! 🎯",
782
+ "Training Announcement": "Hello {name}! Training session this week. Learn new strategies to grow your network! 📚",
783
+ "Incentive Program": "Hello {name}! New incentive program launched. Earn more with higher conversions! 💰",
784
+ "Urgent Meeting": "Hello {name}! Urgent meeting tomorrow. Your attendance is important! ⏰",
785
+ "Custom Message": ""
786
+ }
787
+
788
+ message = st.text_area("Message Content",
789
+ value=message_templates[communication_type],
790
+ height=100)
791
+
792
+ # Personalization options
793
+ st.write("**🎨 Personalization Options**")
794
+ col1, col2 = st.columns(2)
795
+
796
+ with col1:
797
+ include_performance = st.checkbox("Include Performance Data", value=True)
798
+ include_village = st.checkbox("Include Village", value=True)
799
+
800
+ with col2:
801
+ urgent_tag = st.checkbox("Mark as Urgent", value=False)
802
+ request_response = st.checkbox("Request Response", value=True)
803
+
804
+ # Send communication
805
+ if st.button("📱 Send to Distributors", type="primary"):
806
+ # Filter target distributors
807
+ target_distributors = filter_distributors_by_criteria(distributors_data, target_group)
808
+
809
+ if not target_distributors.empty:
810
+ st.success(f"✅ Ready to send message to {len(target_distributors)} distributors")
811
+
812
+ # Show preview
813
+ sample_dist = target_distributors.iloc[0]
814
+ preview_message = personalize_message(message, sample_dist, include_performance, include_village)
815
+ st.write("**Preview:**", preview_message)
816
+ else:
817
+ st.warning("No distributors match the selected criteria")
818
+
819
+ # Team Performance Alerts
820
+ st.subheader("🚨 Performance Alerts")
821
+
822
+ # Low performers alert
823
+ low_performers = distributors_data[
824
+ (distributors_data['sabhasad_count'] < 3) &
825
+ (distributors_data['status'] == 'Active')
826
+ ]
827
+
828
+ if not low_performers.empty:
829
+ st.warning(f"🚨 {len(low_performers)} distributors have less than 3 sabhasad")
830
+ if st.button("🔄 Schedule Support Calls"):
831
+ st.info("Support calls scheduled with underperforming distributors")
832
+
833
+ # High performer recognition
834
+ high_performers = distributors_data[distributors_data['sabhasad_count'] >= 15]
835
+ if not high_performers.empty:
836
+ st.success(f"🏆 {len(high_performers)} elite performers with 15+ sabhasad")
837
+ if st.button("🎉 Send Recognition"):
838
+ st.info("Recognition messages sent to top performers")
839
+
840
+ except Exception as e:
841
+ st.error(f"Error in team management: {e}")
842
+
843
+ def show_distributor_directory_tab(db):
844
+ """Show comprehensive distributor directory"""
845
+ st.subheader("🔍 Distributor Directory")
846
+
847
+ try:
848
+ distributors_data = get_distributor_analytics_data(db)
849
+
850
+ if distributors_data.empty:
851
+ st.info("No distributors found in the database.")
852
+ return
853
+
854
+ # Advanced filtering
855
+ st.subheader("🔍 Advanced Filters")
856
+
857
+ col1, col2, col3 = st.columns(3)
858
+
859
+ with col1:
860
+ village_filter = st.multiselect("Filter by Village", distributors_data['village'].unique())
861
+ performance_filter = st.selectbox("Performance Tier",
862
+ ["All", "Platinum", "Gold", "Silver", "Bronze"])
863
+
864
+ with col2:
865
+ sabhasad_min = st.number_input("Min Sabhasad", 0, 100, 0)
866
+ sabhasad_max = st.number_input("Max Sabhasad", 0, 100, 100)
867
+
868
+ with col3:
869
+ status_filter = st.multiselect("Status", distributors_data['status'].unique(),
870
+ default=['Active'])
871
+ search_term = st.text_input("Search by Name/Village")
872
+
873
+ # Apply filters
874
+ filtered_data = distributors_data.copy()
875
+
876
+ if village_filter:
877
+ filtered_data = filtered_data[filtered_data['village'].isin(village_filter)]
878
+
879
+ if performance_filter != "All":
880
+ filtered_data = filtered_data[filtered_data['performance_tier'] == performance_filter]
881
+
882
+ filtered_data = filtered_data[
883
+ (filtered_data['sabhasad_count'] >= sabhasad_min) &
884
+ (filtered_data['sabhasad_count'] <= sabhasad_max)
885
+ ]
886
+
887
+ if status_filter:
888
+ filtered_data = filtered_data[filtered_data['status'].isin(status_filter)]
889
+
890
+ if search_term:
891
+ filtered_data = filtered_data[
892
+ filtered_data['name'].str.contains(search_term, case=False, na=False) |
893
+ filtered_data['village'].str.contains(search_term, case=False, na=False)
894
+ ]
895
+
896
+ # Display results
897
+ st.write(f"**Found {len(filtered_data)} distributors**")
898
+
899
+ display_columns = ['name', 'village', 'taluka', 'mantri_name', 'sabhasad_count',
900
+ 'contact_in_group', 'performance_tier', 'status']
901
+ display_df = filtered_data[display_columns]
902
+ display_df.columns = ['Name', 'Village', 'Taluka', 'Mantri', 'Sabhasad', 'Contacts', 'Tier', 'Status']
903
+
904
+ st.dataframe(display_df, use_container_width=True)
905
+
906
+ # Export options
907
+ if st.button("📥 Export Distributor Data"):
908
+ csv = filtered_data.to_csv(index=False)
909
+ st.download_button(
910
+ label="Download CSV",
911
+ data=csv,
912
+ file_name=f"distributors_export_{datetime.now().strftime('%Y%m%d')}.csv",
913
+ mime="text/csv"
914
+ )
915
+
916
+ except Exception as e:
917
+ st.error(f"Error loading distributor directory: {e}")
918
+
919
+ def get_distributor_analytics_data(db):
920
+ """Get comprehensive distributor data with analytics"""
921
+ try:
922
+ distributors = db.get_dataframe('distributors', '''
923
+ SELECT d.*,
924
+ COUNT(DISTINCT c.customer_id) as total_customers,
925
+ COALESCE(SUM(s.total_amount), 0) as territory_sales
926
+ FROM distributors d
927
+ LEFT JOIN customers c ON d.village = c.village AND d.taluka = c.taluka
928
+ LEFT JOIN sales s ON c.customer_id = s.customer_id
929
+ GROUP BY d.distributor_id
930
+ ORDER BY d.sabhasad_count DESC
931
+ ''')
932
+ return distributors
933
+ except Exception as e:
934
+ st.error(f"Error loading distributor analytics data: {e}")
935
+ return pd.DataFrame()
936
+
937
+ def get_customer_analytics_data(db):
938
+ """Get customer data for territory analysis"""
939
+ try:
940
+ customers = db.get_dataframe('customers', "SELECT * FROM customers")
941
+ return customers
942
+ except Exception as e:
943
+ return pd.DataFrame()
944
+
945
+ def filter_distributors_by_criteria(distributors_data, criteria):
946
+ """Filter distributors based on selection criteria"""
947
+ if criteria == "All Distributors":
948
+ return distributors_data
949
+ elif criteria == "High Performers":
950
+ return distributors_data[distributors_data['sabhasad_count'] >= 10]
951
+ elif criteria == "Underperformers":
952
+ return distributors_data[distributors_data['sabhasad_count'] < 5]
953
+ elif criteria == "Specific Village":
954
+ # This would need a village selection UI in real implementation
955
+ return distributors_data
956
+ elif criteria == "Performance Tier":
957
+ # This would need a tier selection UI
958
+ return distributors_data
959
+ return distributors_data
960
+
961
+ def personalize_message(message, distributor, include_performance=True, include_village=True):
962
+ """Personalize message for distributor"""
963
+ personalized = message.replace('{name}', distributor['name'])
964
+
965
+ if include_performance and '{sabhasad_count}' in message:
966
+ personalized = personalized.replace('{sabhasad_count}', str(distributor['sabhasad_count']))
967
+
968
+ if include_village and '{village}' in message:
969
+ personalized = personalized.replace('{village}', distributor.get('village', ''))
970
+
971
+ return personalized
pages/file_viewer.py ADDED
@@ -0,0 +1,382 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # pages/file_viewer.py
2
+ import streamlit as st
3
+ import pandas as pd
4
+ import os
5
+ import glob
6
+ import chardet
7
+ from datetime import datetime
8
+ import re
9
+
10
+ try:
11
+ from deep_translator import GoogleTranslator
12
+ TRANSLATOR_AVAILABLE = True
13
+ except ImportError:
14
+ TRANSLATOR_AVAILABLE = False
15
+
16
+ def show_file_viewer_page(db=None, data_processor=None):
17
+ """Universal file viewer for any Excel/CSV file with advanced Gujarati to English conversion"""
18
+ st.title("🔍 Universal File Viewer")
19
+ st.markdown("View and analyze any Excel or CSV file, with advanced Gujarati to English conversion using AI translation")
20
+
21
+ if not TRANSLATOR_AVAILABLE:
22
+ st.error("""
23
+ **Translation features require deep-translator**
24
+ Install with: `pip install deep-translator`
25
+
26
+ Without this, only basic number conversion will work.
27
+ """)
28
+
29
+ # File selection options
30
+ tab1, tab2 = st.tabs(["📁 Browse Data Folder", "📤 Upload New File"])
31
+
32
+ with tab1:
33
+ show_data_folder_browser()
34
+
35
+ with tab2:
36
+ show_file_uploader()
37
+
38
+ def show_data_folder_browser():
39
+ """Browse and view files from the data folder"""
40
+ data_dir = "data"
41
+
42
+ if not os.path.exists(data_dir):
43
+ os.makedirs(data_dir, exist_ok=True)
44
+ st.info("Data folder created. Upload files to get started.")
45
+ return
46
+
47
+ # Get all supported files
48
+ excel_files = glob.glob(os.path.join(data_dir, "*.xlsx")) + glob.glob(os.path.join(data_dir, "*.xls"))
49
+ csv_files = glob.glob(os.path.join(data_dir, "*.csv"))
50
+ all_files = excel_files + csv_files
51
+
52
+ if not all_files:
53
+ st.info("No Excel or CSV files found in the data folder.")
54
+ return
55
+
56
+ # File selection
57
+ st.subheader("📂 Select File to View")
58
+
59
+ file_options = {os.path.basename(f): f for f in all_files}
60
+ selected_file_name = st.selectbox("Choose a file", options=list(file_options.keys()))
61
+
62
+ if selected_file_name:
63
+ file_path = file_options[selected_file_name]
64
+ display_file_content(file_path, selected_file_name)
65
+
66
+ def show_file_uploader():
67
+ """Upload and view new files"""
68
+ st.subheader("📤 Upload New File")
69
+
70
+ uploaded_file = st.file_uploader(
71
+ "Choose Excel or CSV file",
72
+ type=['xlsx', 'xls', 'csv'],
73
+ help="Upload Excel (.xlsx, .xls) or CSV files for viewing"
74
+ )
75
+
76
+ if uploaded_file:
77
+ # Save to data folder for processing
78
+ file_path = os.path.join("data", uploaded_file.name)
79
+ with open(file_path, "wb") as f:
80
+ f.write(uploaded_file.getbuffer())
81
+
82
+ display_file_content(file_path, uploaded_file.name)
83
+
84
+ def display_file_content(file_path, file_name):
85
+ """Display file content with conversion options"""
86
+ try:
87
+ # File info
88
+ file_size = os.path.getsize(file_path) / 1024
89
+ file_mtime = datetime.fromtimestamp(os.path.getmtime(file_path))
90
+
91
+ col1, col2, col3 = st.columns(3)
92
+ with col1:
93
+ st.metric("File", file_name)
94
+ with col2:
95
+ st.metric("Size", f"{file_size:.1f} KB")
96
+ with col3:
97
+ st.metric("Modified", file_mtime.strftime('%Y-%m-%d %H:%M'))
98
+
99
+ # Conversion options
100
+ st.subheader("🔄 Conversion Options")
101
+
102
+ col1, col2 = st.columns(2)
103
+ with col1:
104
+ convert_gujarati = st.checkbox("Convert Gujarati to English", value=True)
105
+ with col2:
106
+ use_ai_translation = st.checkbox("Use AI Translation",
107
+ value=TRANSLATOR_AVAILABLE,
108
+ disabled=not TRANSLATOR_AVAILABLE)
109
+
110
+ # Read file
111
+ if file_path.endswith('.csv'):
112
+ df = read_csv_file(file_path)
113
+ else:
114
+ df = read_excel_file(file_path)
115
+
116
+ if df is None or df.empty:
117
+ st.warning("No data found in the file.")
118
+ return
119
+
120
+ # Show original data
121
+ st.subheader("📝 Original Data")
122
+ display_dataframe_info(df, "Original")
123
+
124
+ # Apply conversion if requested
125
+ if convert_gujarati:
126
+ with st.spinner("Converting Gujarati content..."):
127
+ df_converted = convert_gujarati_data_advanced(df, use_ai_translation)
128
+
129
+ st.subheader("🔤 Converted Data (Gujarati → English)")
130
+ display_dataframe_info(df_converted, "Converted")
131
+
132
+ # Show conversion summary
133
+ show_conversion_summary(df, df_converted)
134
+
135
+ # Data analysis tools
136
+ st.subheader("🔧 Data Analysis Tools")
137
+ show_data_analysis_tools(df_converted if convert_gujarati else df)
138
+
139
+ except Exception as e:
140
+ st.error(f"Error processing file: {str(e)}")
141
+
142
+ def show_conversion_summary(original_df, converted_df):
143
+ """Show summary of Gujarati to English conversion"""
144
+ st.subheader("📊 Conversion Summary")
145
+
146
+ changes_detected = False
147
+ conversion_examples = []
148
+
149
+ # Check for changes
150
+ for i in range(min(3, len(original_df))):
151
+ for col in original_df.columns[:3]:
152
+ if i < len(original_df):
153
+ orig_val = str(original_df.iloc[i][col])
154
+ conv_val = str(converted_df.iloc[i][col])
155
+ if orig_val != conv_val and contains_gujarati(orig_val):
156
+ changes_detected = True
157
+ conversion_examples.append(f"`{orig_val}` → `{conv_val}`")
158
+ break
159
+
160
+ if changes_detected:
161
+ st.success("✅ Gujarati content was detected and converted to English")
162
+ if conversion_examples:
163
+ st.write("**Conversion Examples:**")
164
+ for example in conversion_examples:
165
+ st.write(example)
166
+ else:
167
+ st.info("ℹ️ No Gujarati content detected - data is already in English")
168
+
169
+ # Helper functions for file reading and conversion
170
+ def read_csv_file(file_path):
171
+ """Read CSV file with automatic encoding detection"""
172
+ try:
173
+ # Try reading with different encodings
174
+ for enc in ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']:
175
+ try:
176
+ return pd.read_csv(file_path, encoding=enc)
177
+ except:
178
+ continue
179
+
180
+ # Last resort
181
+ return pd.read_csv(file_path, encoding='utf-8', errors='ignore')
182
+ except Exception as e:
183
+ st.error(f"Error reading CSV: {str(e)}")
184
+ return pd.DataFrame()
185
+
186
+ def read_excel_file(file_path):
187
+ """Read Excel file with all sheets"""
188
+ try:
189
+ excel_file = pd.ExcelFile(file_path)
190
+
191
+ if len(excel_file.sheet_names) == 1:
192
+ return pd.read_excel(file_path)
193
+ else:
194
+ sheet_name = st.selectbox(
195
+ "Select Sheet to View",
196
+ options=excel_file.sheet_names,
197
+ key=f"sheet_select_{file_path}"
198
+ )
199
+ return pd.read_excel(file_path, sheet_name=sheet_name)
200
+ except Exception as e:
201
+ st.error(f"Error reading Excel file: {str(e)}")
202
+ return pd.DataFrame()
203
+
204
+ def convert_gujarati_data_advanced(df, use_ai_translation=False):
205
+ """Convert Gujarati content to English using advanced methods"""
206
+ try:
207
+ df_converted = df.copy()
208
+
209
+ # Convert column names
210
+ df_converted.columns = [convert_gujarati_text(col, use_ai_translation) for col in df_converted.columns]
211
+
212
+ # Convert data in each column
213
+ for col in df_converted.columns:
214
+ df_converted[col] = df_converted[col].astype(str)
215
+ df_converted[col] = df_converted[col].apply(
216
+ lambda x: convert_gujarati_text(x, use_ai_translation)
217
+ )
218
+
219
+ return df_converted
220
+ except Exception as e:
221
+ st.warning(f"Conversion issues: {str(e)}")
222
+ return df
223
+
224
+ def convert_gujarati_text(text, use_ai_translation=False):
225
+ """Convert Gujarati text to English using multiple methods"""
226
+ if not isinstance(text, str) or not text.strip():
227
+ return text
228
+
229
+ # Step 1: Always convert Gujarati numbers
230
+ text = gujarati_to_english_digits(text)
231
+
232
+ # Step 2: Check if text contains Gujarati characters
233
+ if contains_gujarati(text):
234
+ if use_ai_translation and TRANSLATOR_AVAILABLE:
235
+ try:
236
+ return GoogleTranslator(source='gu', target='en').translate(text)
237
+ except Exception as e:
238
+ st.warning(f"Translation failed for '{text}': {str(e)}")
239
+ return apply_basic_gujarati_conversion(text)
240
+ else:
241
+ return apply_basic_gujarati_conversion(text)
242
+ else:
243
+ return text
244
+
245
+ def gujarati_to_english_digits(text):
246
+ """Convert Gujarati numbers to English digits"""
247
+ gujarati_to_english_numbers = {
248
+ '૦': '0', '૧': '1', '૨': '2', '૩': '3', '૪': '4',
249
+ '૫': '5', '૬': '6', '૭': '7', '૮': '8', '૯': '9'
250
+ }
251
+
252
+ converted_text = text
253
+ for guj, eng in gujarati_to_english_numbers.items():
254
+ converted_text = converted_text.replace(guj, eng)
255
+
256
+ return converted_text
257
+
258
+ def contains_gujarati(text):
259
+ """Check if text contains Gujarati characters"""
260
+ gujarati_pattern = re.compile(r'[\u0A80-\u0AFF]')
261
+ return bool(gujarati_pattern.search(text))
262
+
263
+ def apply_basic_gujarati_conversion(text):
264
+ """Apply basic Gujarati to English conversion for common words"""
265
+ gujarati_to_english_words = {
266
+ 'ગ્રાહક': 'Customer', 'નામ': 'Name', 'મોબાઈલ': 'Mobile', 'ફોન': 'Phone',
267
+ 'ગામ': 'Village', 'તાલુકો': 'Taluka', 'જિલ્લો': 'District', 'શહેર': 'City',
268
+ 'બીલ': 'Bill', 'ચલણ': 'Invoice', 'રકમ': 'Amount', 'પ્રમાણ': 'Quantity',
269
+ 'ઉત્પાદન': 'Product', 'તારીખ': 'Date', 'ચુકવણી': 'Payment'
270
+ }
271
+
272
+ converted_text = text
273
+ for guj, eng in gujarati_to_english_words.items():
274
+ converted_text = converted_text.replace(guj, eng)
275
+
276
+ return converted_text
277
+
278
+ def display_dataframe_info(df, title):
279
+ """Display dataframe with comprehensive information"""
280
+ st.write(f"**{title} Data Summary**")
281
+
282
+ # Basic info
283
+ col1, col2, col3, col4 = st.columns(4)
284
+ with col1:
285
+ st.metric("Rows", len(df))
286
+ with col2:
287
+ st.metric("Columns", len(df.columns))
288
+ with col3:
289
+ non_empty = len(df.dropna(how='all'))
290
+ st.metric("Non-empty Rows", non_empty)
291
+ with col4:
292
+ empty_cells = df.isna().sum().sum()
293
+ st.metric("Empty Cells", empty_cells)
294
+
295
+ # Column information
296
+ st.subheader("📋 Column Details")
297
+ col_info = []
298
+ for col in df.columns:
299
+ col_info.append({
300
+ 'Column Name': col,
301
+ 'Data Type': str(df[col].dtype),
302
+ 'Non-Null Count': df[col].count(),
303
+ 'Null Count': df[col].isna().sum(),
304
+ 'Unique Values': df[col].nunique()
305
+ })
306
+
307
+ col_info_df = pd.DataFrame(col_info)
308
+ st.dataframe(col_info_df, use_container_width=True)
309
+
310
+ # Data preview
311
+ st.subheader("👀 Data Preview")
312
+ show_rows = st.slider("Number of rows to show", 5, 100, 10, key=f"rows_{title}")
313
+ st.dataframe(df.head(show_rows), use_container_width=True)
314
+
315
+ def show_data_analysis_tools(df):
316
+ """Show basic data analysis tools"""
317
+ tab1, tab2, tab3 = st.tabs(["📈 Basic Stats", "🔍 Search & Filter", "💾 Export"])
318
+
319
+ with tab1:
320
+ show_basic_stats(df)
321
+
322
+ with tab2:
323
+ show_search_filter(df)
324
+
325
+ with tab3:
326
+ show_export_options(df)
327
+
328
+ def show_basic_stats(df):
329
+ """Show basic statistical analysis"""
330
+ st.write("**Numerical Columns Statistics**")
331
+
332
+ numerical_cols = df.select_dtypes(include=['number']).columns
333
+ if len(numerical_cols) > 0:
334
+ st.dataframe(df[numerical_cols].describe(), use_container_width=True)
335
+ else:
336
+ st.info("No numerical columns found for statistical analysis")
337
+
338
+ def show_search_filter(df):
339
+ """Show search and filter options"""
340
+ st.write("**Search in Data**")
341
+
342
+ search_term = st.text_input("Search term")
343
+ if search_term:
344
+ # Search across all string columns
345
+ mask = pd.Series([False] * len(df))
346
+ for col in df.columns:
347
+ if df[col].dtype == 'object':
348
+ mask = mask | df[col].astype(str).str.contains(search_term, case=False, na=False)
349
+
350
+ filtered_df = df[mask]
351
+ st.write(f"Found {len(filtered_df)} matching rows")
352
+ st.dataframe(filtered_df.head(20), use_container_width=True)
353
+
354
+ def show_export_options(df):
355
+ """Show data export options"""
356
+ st.write("**Export Processed Data**")
357
+
358
+ col1, col2 = st.columns(2)
359
+
360
+ with col1:
361
+ csv = df.to_csv(index=False)
362
+ st.download_button(
363
+ label="📥 Download as CSV",
364
+ data=csv,
365
+ file_name="converted_data.csv",
366
+ mime="text/csv"
367
+ )
368
+
369
+ with col2:
370
+ # For Excel, we need to save to a file first
371
+ excel_file = "converted_data.xlsx"
372
+ df.to_excel(excel_file, index=False)
373
+ with open(excel_file, "rb") as f:
374
+ st.download_button(
375
+ label="📊 Download as Excel",
376
+ data=f,
377
+ file_name=excel_file,
378
+ mime="application/vnd.ms-excel"
379
+ )
380
+ # Clean up
381
+ if os.path.exists(excel_file):
382
+ os.remove(excel_file)
pages/payments.py ADDED
@@ -0,0 +1,524 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # pages/payments.py
2
+ import streamlit as st
3
+ import pandas as pd
4
+ import plotly.express as px
5
+ from datetime import datetime, timedelta
6
+
7
+ def show_payments_page(db, whatsapp_manager=None):
8
+ """Show payments management and tracking page"""
9
+ st.title("💳 Payments Management")
10
+
11
+ if not db:
12
+ st.error("Database not available. Please check initialization.")
13
+ return
14
+
15
+ # Tabs for different payment functions
16
+ tab1, tab2, tab3, tab4 = st.tabs(["💰 Record Payment", "📋 Payment History", "⏳ Pending Payments", "📊 Payment Analytics"])
17
+
18
+ with tab1:
19
+ show_record_payment_tab(db, whatsapp_manager)
20
+
21
+ with tab2:
22
+ show_payment_history_tab(db)
23
+
24
+ with tab3:
25
+ show_pending_payments_tab(db, whatsapp_manager)
26
+
27
+ with tab4:
28
+ show_payment_analytics_tab(db)
29
+
30
+ def show_record_payment_tab(db, whatsapp_manager):
31
+ """Show form to record new payments"""
32
+ st.subheader("💰 Record New Payment")
33
+
34
+ with st.form("record_payment_form"):
35
+ st.markdown("### 📄 Payment Information")
36
+
37
+ col1, col2 = st.columns(2)
38
+
39
+ with col1:
40
+ # Get pending sales for selection
41
+ pending_sales = get_pending_sales(db)
42
+ if not pending_sales.empty:
43
+ sale_options = {f"{row['invoice_no']} - {row['customer_name']} (₹{row['pending_amount']:,.2f})": row['sale_id']
44
+ for _, row in pending_sales.iterrows()}
45
+ selected_sale = st.selectbox("Select Sale*", options=list(sale_options.keys()))
46
+ sale_id = sale_options[selected_sale] if selected_sale else None
47
+
48
+ # Show sale details
49
+ if sale_id:
50
+ sale_details = pending_sales[pending_sales['sale_id'] == sale_id].iloc[0]
51
+ st.info(f"**Sale Details:** {sale_details['customer_name']} - Pending: ₹{sale_details['pending_amount']:,.2f}")
52
+ else:
53
+ st.warning("No pending sales found. All sales are fully paid!")
54
+ sale_id = None
55
+
56
+ with col2:
57
+ payment_date = st.date_input("Payment Date*", datetime.now())
58
+ payment_method = st.selectbox("Payment Method*",
59
+ ["Cash", "G-Pay", "PhonePe", "Bank Transfer", "Cheque", "Other"])
60
+ payment_status = st.selectbox("Payment Status", ["Completed", "Pending", "Failed"])
61
+
62
+ st.markdown("### 💵 Payment Amount")
63
+
64
+ col1, col2 = st.columns(2)
65
+
66
+ with col1:
67
+ if sale_id:
68
+ sale_data = pending_sales[pending_sales['sale_id'] == sale_id].iloc[0]
69
+ max_amount = sale_data['pending_amount']
70
+ payment_amount = st.number_input("Payment Amount*", min_value=0.0, max_value=float(max_amount),
71
+ value=float(max_amount), step=100.0)
72
+ st.write(f"Pending Amount: ₹{max_amount:,.2f}")
73
+ else:
74
+ payment_amount = st.number_input("Payment Amount*", min_value=0.0, value=0.0, step=100.0)
75
+
76
+ with col2:
77
+ reference_number = st.text_input("Reference Number",
78
+ placeholder="UPI ID, Cheque No, Transaction ID, etc.")
79
+ rrn_number = st.text_input("RRN Number", placeholder="For bank transfers")
80
+
81
+ notes = st.text_area("Payment Notes", placeholder="Any additional notes about this payment...")
82
+
83
+ # Submit button
84
+ submitted = st.form_submit_button("💳 Record Payment", type="primary")
85
+
86
+ if submitted:
87
+ # Validation
88
+ errors = []
89
+ if not sale_id:
90
+ errors.append("Sale selection is required")
91
+ if not payment_amount or payment_amount <= 0:
92
+ errors.append("Valid payment amount is required")
93
+ if not payment_date:
94
+ errors.append("Payment date is required")
95
+ if not payment_method:
96
+ errors.append("Payment method is required")
97
+
98
+ if errors:
99
+ for error in errors:
100
+ st.error(f"❌ {error}")
101
+ else:
102
+ try:
103
+ # Record payment in database
104
+ payment_id = add_payment_to_database(db, {
105
+ 'sale_id': sale_id,
106
+ 'payment_date': payment_date,
107
+ 'payment_method': payment_method,
108
+ 'amount': payment_amount,
109
+ 'rrn': rrn_number,
110
+ 'reference': reference_number,
111
+ 'status': payment_status,
112
+ 'notes': notes
113
+ })
114
+
115
+ if payment_id and payment_id > 0:
116
+ st.success(f"✅ Payment recorded successfully! Payment ID: {payment_id}")
117
+
118
+ # Update sale payment status
119
+ update_sale_payment_status(db, sale_id)
120
+
121
+ # Send notification if WhatsApp available
122
+ if whatsapp_manager and sale_id:
123
+ send_payment_notification(whatsapp_manager, db, sale_id, payment_amount)
124
+
125
+ # Show payment summary
126
+ show_payment_summary(db, payment_id)
127
+
128
+ else:
129
+ st.error("❌ Failed to record payment. Please try again.")
130
+
131
+ except Exception as e:
132
+ st.error(f"❌ Error recording payment: {e}")
133
+
134
+ def get_pending_sales(db):
135
+ """Get sales with pending payments"""
136
+ try:
137
+ return db.get_dataframe('sales', '''
138
+ SELECT s.sale_id, s.invoice_no, s.total_amount, s.payment_status,
139
+ c.name as customer_name, c.mobile, c.village,
140
+ (s.total_amount - COALESCE(SUM(p.amount), 0)) as pending_amount
141
+ FROM sales s
142
+ LEFT JOIN customers c ON s.customer_id = c.customer_id
143
+ LEFT JOIN payments p ON s.sale_id = p.sale_id
144
+ WHERE s.payment_status IN ('Pending', 'Partial')
145
+ GROUP BY s.sale_id
146
+ HAVING pending_amount > 0
147
+ ORDER BY s.sale_date DESC
148
+ ''')
149
+ except Exception as e:
150
+ st.error(f"Error getting pending sales: {e}")
151
+ return pd.DataFrame()
152
+
153
+ def add_payment_to_database(db, payment_data):
154
+ """Add payment record to database"""
155
+ try:
156
+ db.execute_query('''
157
+ INSERT INTO payments (sale_id, payment_date, payment_method, amount, rrn, reference, status, notes)
158
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
159
+ ''', (
160
+ payment_data['sale_id'],
161
+ payment_data['payment_date'],
162
+ payment_data['payment_method'],
163
+ payment_data['amount'],
164
+ payment_data['rrn'],
165
+ payment_data['reference'],
166
+ payment_data['status'],
167
+ payment_data['notes']
168
+ ), log_action=False)
169
+
170
+ # Get the inserted payment_id
171
+ result = db.execute_query('SELECT last_insert_rowid()', log_action=False)
172
+ return result[0][0] if result else -1
173
+
174
+ except Exception as e:
175
+ st.error(f"Database error: {e}")
176
+ return -1
177
+
178
+ def update_sale_payment_status(db, sale_id):
179
+ """Update sale payment status based on payments"""
180
+ try:
181
+ # Get total paid amount
182
+ payments_data = db.get_dataframe('payments', f'''
183
+ SELECT COALESCE(SUM(amount), 0) as total_paid
184
+ FROM payments
185
+ WHERE sale_id = {sale_id} AND status = 'Completed'
186
+ ''')
187
+
188
+ if not payments_data.empty:
189
+ total_paid = payments_data.iloc[0]['total_paid']
190
+
191
+ # Get sale total
192
+ sale_data = db.get_dataframe('sales', f'SELECT total_amount FROM sales WHERE sale_id = {sale_id}')
193
+ if not sale_data.empty:
194
+ sale_total = sale_data.iloc[0]['total_amount']
195
+
196
+ # Determine payment status
197
+ if total_paid >= sale_total:
198
+ new_status = 'Paid'
199
+ elif total_paid > 0:
200
+ new_status = 'Partial'
201
+ else:
202
+ new_status = 'Pending'
203
+
204
+ # Update sale status
205
+ db.execute_query('''
206
+ UPDATE sales SET payment_status = ?, updated_date = CURRENT_TIMESTAMP
207
+ WHERE sale_id = ?
208
+ ''', (new_status, sale_id), log_action=False)
209
+
210
+ except Exception as e:
211
+ st.error(f"Error updating payment status: {e}")
212
+
213
+ def send_payment_notification(whatsapp_manager, db, sale_id, payment_amount):
214
+ """Send payment confirmation to customer"""
215
+ try:
216
+ # Get sale and customer details
217
+ sale_data = db.get_dataframe('sales', f'''
218
+ SELECT s.*, c.name as customer_name, c.mobile
219
+ FROM sales s
220
+ LEFT JOIN customers c ON s.customer_id = c.customer_id
221
+ WHERE s.sale_id = {sale_id}
222
+ ''')
223
+
224
+ if not sale_data.empty:
225
+ sale = sale_data.iloc[0]
226
+
227
+ if sale.get('mobile'):
228
+ message = f"""Hello {sale['customer_name']}! 💰
229
+
230
+ We have received your payment of ₹{payment_amount:,.2f} for invoice {sale['invoice_no']}.
231
+
232
+ Thank you for your prompt payment!
233
+
234
+ If you have any questions, please feel free to contact us.
235
+
236
+ Best regards,
237
+ Sales Team"""
238
+
239
+ success = whatsapp_manager.send_message(sale['mobile'], message)
240
+ if success:
241
+ st.success("📱 Payment confirmation sent to customer!")
242
+ else:
243
+ st.warning("⚠️ Could not send payment confirmation")
244
+
245
+ except Exception as e:
246
+ st.warning(f"Could not send payment notification: {e}")
247
+
248
+ def show_payment_summary(db, payment_id):
249
+ """Show summary of recorded payment"""
250
+ try:
251
+ payment_data = db.get_dataframe('payments', f'''
252
+ SELECT p.*, s.invoice_no, s.total_amount, c.name as customer_name, c.village
253
+ FROM payments p
254
+ LEFT JOIN sales s ON p.sale_id = s.sale_id
255
+ LEFT JOIN customers c ON s.customer_id = c.customer_id
256
+ WHERE p.payment_id = {payment_id}
257
+ ''')
258
+
259
+ if not payment_data.empty:
260
+ payment = payment_data.iloc[0]
261
+
262
+ st.markdown("## 🎉 Payment Recorded Successfully!")
263
+
264
+ col1, col2 = st.columns(2)
265
+
266
+ with col1:
267
+ st.subheader("💳 Payment Details")
268
+ st.write(f"**Payment ID:** {payment_id}")
269
+ st.write(f"**Invoice No:** {payment['invoice_no']}")
270
+ st.write(f"**Customer:** {payment['customer_name']}")
271
+ st.write(f"**Village:** {payment['village']}")
272
+ st.write(f"**Payment Method:** {payment['payment_method']}")
273
+
274
+ with col2:
275
+ st.subheader("💰 Amount & Status")
276
+ st.write(f"**Amount Paid:** ₹{payment['amount']:,.2f}")
277
+ st.write(f"**Sale Total:** ₹{payment['total_amount']:,.2f}")
278
+ st.write(f"**Payment Date:** {payment['payment_date']}")
279
+ st.write(f"**Status:** {payment['status']}")
280
+
281
+ if payment['reference']:
282
+ st.write(f"**Reference:** {payment['reference']}")
283
+
284
+ # Quick actions
285
+ st.markdown("### ⚡ Quick Actions")
286
+ col1, col2, col3 = st.columns(3)
287
+
288
+ with col1:
289
+ if st.button("📋 View Payment History"):
290
+ st.session_state.current_tab = "📋 Payment History"
291
+
292
+ with col2:
293
+ if st.button("💰 Record Another"):
294
+ st.rerun()
295
+
296
+ with col3:
297
+ if st.button("⏳ View Pending"):
298
+ st.session_state.current_tab = "⏳ Pending Payments"
299
+
300
+ except Exception as e:
301
+ st.error(f"Error displaying payment summary: {e}")
302
+
303
+ def show_payment_history_tab(db):
304
+ """Show payment history and records"""
305
+ st.subheader("📋 Payment History")
306
+
307
+ try:
308
+ # Date range filter
309
+ col1, col2 = st.columns(2)
310
+ with col1:
311
+ start_date = st.date_input("Start Date", datetime.now() - timedelta(days=30))
312
+ with col2:
313
+ end_date = st.date_input("End Date", datetime.now())
314
+
315
+ # Status filter
316
+ status_filter = st.multiselect("Filter by Status",
317
+ ["Completed", "Pending", "Failed"],
318
+ default=["Completed"])
319
+
320
+ # Method filter
321
+ methods = db.get_dataframe('payments', "SELECT DISTINCT payment_method FROM payments")
322
+ if not methods.empty:
323
+ method_options = methods['payment_method'].dropna().unique().tolist()
324
+ method_filter = st.multiselect("Filter by Method", method_options, default=method_options)
325
+
326
+ # Get payments data
327
+ payments_data = get_payments_data(db, start_date, end_date, status_filter, method_filter)
328
+
329
+ if not payments_data.empty:
330
+ st.write(f"**💰 Showing {len(payments_data)} payments**")
331
+
332
+ # Display payments
333
+ display_data = payments_data[['payment_date', 'customer_name', 'invoice_no', 'amount',
334
+ 'payment_method', 'status', 'reference']].copy()
335
+ display_data.columns = ['Date', 'Customer', 'Invoice', 'Amount', 'Method', 'Status', 'Reference']
336
+ display_data['Amount'] = display_data['Amount'].apply(lambda x: f"₹{x:,.2f}")
337
+ display_data = display_data.sort_values('Date', ascending=False)
338
+
339
+ st.dataframe(display_data, use_container_width=True)
340
+
341
+ # Payment statistics
342
+ st.subheader("📊 Payment Statistics")
343
+ col1, col2, col3, col4 = st.columns(4)
344
+
345
+ with col1:
346
+ total_payments = len(payments_data)
347
+ st.metric("Total Payments", total_payments)
348
+
349
+ with col2:
350
+ total_amount = payments_data['amount'].sum()
351
+ st.metric("Total Amount", f"₹{total_amount:,.2f}")
352
+
353
+ with col3:
354
+ completed = len(payments_data[payments_data['status'] == 'Completed'])
355
+ st.metric("Completed", completed)
356
+
357
+ with col4:
358
+ avg_payment = payments_data['amount'].mean()
359
+ st.metric("Avg Payment", f"₹{avg_payment:,.0f}")
360
+
361
+ else:
362
+ st.info("No payments found for the selected criteria.")
363
+
364
+ except Exception as e:
365
+ st.error(f"Error loading payment history: {e}")
366
+
367
+ def get_payments_data(db, start_date, end_date, status_filter, method_filter):
368
+ """Get payments data with filters"""
369
+ try:
370
+ query = '''
371
+ SELECT p.*, s.invoice_no, c.name as customer_name, c.village
372
+ FROM payments p
373
+ LEFT JOIN sales s ON p.sale_id = s.sale_id
374
+ LEFT JOIN customers c ON s.customer_id = c.customer_id
375
+ WHERE p.payment_date BETWEEN ? AND ?
376
+ '''
377
+
378
+ params = [start_date, end_date]
379
+
380
+ if status_filter:
381
+ placeholders = ','.join(['?' for _ in status_filter])
382
+ query += f' AND p.status IN ({placeholders})'
383
+ params.extend(status_filter)
384
+
385
+ if method_filter:
386
+ placeholders = ','.join(['?' for _ in method_filter])
387
+ query += f' AND p.payment_method IN ({placeholders})'
388
+ params.extend(method_filter)
389
+
390
+ query += ' ORDER BY p.payment_date DESC'
391
+
392
+ return db.get_dataframe('payments', query, params=params)
393
+
394
+ except Exception as e:
395
+ st.error(f"Error getting payments data: {e}")
396
+ return pd.DataFrame()
397
+
398
+ def show_pending_payments_tab(db, whatsapp_manager):
399
+ """Show pending payments and reminders"""
400
+ st.subheader("⏳ Pending Payments")
401
+
402
+ try:
403
+ # Get pending payments
404
+ pending_payments = get_pending_sales(db)
405
+
406
+ if not pending_payments.empty:
407
+ st.warning(f"🚨 {len(pending_payments)} Sales with Pending Payments!")
408
+
409
+ # Display pending payments
410
+ display_data = pending_payments[['invoice_no', 'customer_name', 'village', 'total_amount', 'pending_amount', 'payment_status']].copy()
411
+ display_data.columns = ['Invoice', 'Customer', 'Village', 'Total Amount', 'Pending Amount', 'Status']
412
+ display_data['Total Amount'] = display_data['Total Amount'].apply(lambda x: f"₹{x:,.2f}")
413
+ display_data['Pending Amount'] = display_data['Pending Amount'].apply(lambda x: f"₹{x:,.2f}")
414
+
415
+ st.dataframe(display_data, use_container_width=True)
416
+
417
+ # Total pending amount
418
+ total_pending = pending_payments['pending_amount'].sum()
419
+ st.error(f"**💰 Total Pending Amount: ₹{total_pending:,.2f}**")
420
+
421
+ # Send reminders
422
+ st.subheader("📱 Send Payment Reminders")
423
+ selected_invoices = st.multiselect("Select Invoices for Reminders",
424
+ pending_payments['invoice_no'].tolist())
425
+
426
+ if selected_invoices and whatsapp_manager:
427
+ if st.button("📧 Send WhatsApp Reminders"):
428
+ send_bulk_payment_reminders(whatsapp_manager, db, pending_payments, selected_invoices)
429
+ st.success("✅ Payment reminders sent!")
430
+
431
+ elif not whatsapp_manager:
432
+ st.info("📱 WhatsApp manager not available for sending reminders")
433
+
434
+ else:
435
+ st.success("🎉 All payments are cleared! No pending payments.")
436
+
437
+ except Exception as e:
438
+ st.error(f"Error loading pending payments: {e}")
439
+
440
+ def send_bulk_payment_reminders(whatsapp_manager, db, pending_payments, selected_invoices):
441
+ """Send bulk payment reminders"""
442
+ try:
443
+ selected_sales = pending_payments[pending_payments['invoice_no'].isin(selected_invoices)]
444
+
445
+ for _, sale in selected_sales.iterrows():
446
+ if sale.get('mobile'):
447
+ message = f"""Hello {sale['customer_name']}! ⏰
448
+
449
+ Friendly reminder regarding your pending payment.
450
+
451
+ Invoice: {sale['invoice_no']}
452
+ Pending Amount: ₹{sale['pending_amount']:,.2f}
453
+
454
+ Please make the payment at your earliest convenience.
455
+
456
+ Thank you for your cooperation!
457
+
458
+ Best regards,
459
+ Sales Team"""
460
+
461
+ whatsapp_manager.send_message(sale['mobile'], message)
462
+
463
+ except Exception as e:
464
+ st.error(f"Error sending reminders: {e}")
465
+
466
+ def show_payment_analytics_tab(db):
467
+ """Show payment analytics and trends"""
468
+ st.subheader("📊 Payment Analytics")
469
+
470
+ try:
471
+ # Get payments data for analytics
472
+ payments_data = db.get_dataframe('payments', '''
473
+ SELECT p.*, s.invoice_no, c.name as customer_name
474
+ FROM payments p
475
+ LEFT JOIN sales s ON p.sale_id = s.sale_id
476
+ LEFT JOIN customers c ON s.customer_id = c.customer_id
477
+ WHERE p.status = 'Completed'
478
+ ORDER BY p.payment_date DESC
479
+ ''')
480
+
481
+ if not payments_data.empty:
482
+ # Payment method distribution
483
+ st.subheader("💳 Payment Methods Distribution")
484
+ method_stats = payments_data['payment_method'].value_counts()
485
+
486
+ if not method_stats.empty:
487
+ fig = px.pie(values=method_stats.values, names=method_stats.index,
488
+ title='Payment Methods Distribution')
489
+ st.plotly_chart(fig, use_container_width=True)
490
+
491
+ # Monthly payment trend
492
+ st.subheader("📈 Monthly Payment Trend")
493
+ try:
494
+ payments_data['payment_date'] = pd.to_datetime(payments_data['payment_date'])
495
+ monthly_payments = payments_data.groupby(payments_data['payment_date'].dt.to_period('M')).agg({
496
+ 'amount': 'sum',
497
+ 'payment_id': 'count'
498
+ }).reset_index()
499
+ monthly_payments['payment_date'] = monthly_payments['payment_date'].astype(str)
500
+
501
+ if not monthly_payments.empty:
502
+ fig = px.line(monthly_payments, x='payment_date', y='amount',
503
+ title='Monthly Payment Amount Trend',
504
+ labels={'payment_date': 'Month', 'amount': 'Amount (₹)'})
505
+ st.plotly_chart(fig, use_container_width=True)
506
+ except:
507
+ st.info("Could not generate monthly trend chart")
508
+
509
+ # Top customers by payments
510
+ st.subheader("🏆 Top Customers by Payments")
511
+ customer_stats = payments_data.groupby('customer_name').agg({
512
+ 'amount': 'sum',
513
+ 'payment_id': 'count'
514
+ }).reset_index()
515
+ customer_stats.columns = ['Customer', 'Total Paid', 'Payment Count']
516
+ customer_stats = customer_stats.sort_values('Total Paid', ascending=False).head(10)
517
+
518
+ st.dataframe(customer_stats, use_container_width=True)
519
+
520
+ else:
521
+ st.info("No payment data available for analytics.")
522
+
523
+ except Exception as e:
524
+ st.error(f"Error loading payment analytics: {e}")
pages/reports.py ADDED
@@ -0,0 +1,1101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # pages/reports.py
2
+ import streamlit as st
3
+ import pandas as pd
4
+ import plotly.express as px
5
+ import plotly.graph_objects as go
6
+ from datetime import datetime, timedelta
7
+ import numpy as np
8
+
9
+ def show_reports_page(db, whatsapp_manager=None):
10
+ """Show comprehensive business intelligence and reporting"""
11
+ st.title("📈 Business Intelligence & Reports")
12
+
13
+ if not db:
14
+ st.error("Database not available. Please check initialization.")
15
+ return
16
+
17
+ # Tabs for different report types
18
+ tab1, tab2, tab3, tab4, tab5 = st.tabs(["📊 Sales Reports", "👥 Customer Reports",
19
+ "🤝 Distributor Reports", "💰 Financial Reports",
20
+ "🎯 Performance Reports"])
21
+
22
+ with tab1:
23
+ show_sales_reports_tab(db)
24
+
25
+ with tab2:
26
+ show_customer_reports_tab(db)
27
+
28
+ with tab3:
29
+ show_distributor_reports_tab(db)
30
+
31
+ with tab4:
32
+ show_financial_reports_tab(db)
33
+
34
+ with tab5:
35
+ show_performance_reports_tab(db)
36
+
37
+ def show_sales_reports_tab(db):
38
+ """Show sales-related reports and analytics"""
39
+ st.subheader("📊 Sales Performance Reports")
40
+
41
+ # Date range selection
42
+ col1, col2, col3 = st.columns([2, 2, 1])
43
+
44
+ with col1:
45
+ start_date = st.date_input("Start Date", datetime.now() - timedelta(days=30))
46
+ with col2:
47
+ end_date = st.date_input("End Date", datetime.now())
48
+ with col3:
49
+ report_granularity = st.selectbox("Granularity", ["Daily", "Weekly", "Monthly"])
50
+
51
+ try:
52
+ # Sales Summary
53
+ st.subheader("💰 Sales Summary")
54
+ sales_summary = get_sales_summary(db, start_date, end_date)
55
+
56
+ if sales_summary:
57
+ col1, col2, col3, col4, col5 = st.columns(5)
58
+
59
+ with col1:
60
+ st.metric("Total Sales", f"₹{sales_summary.get('total_sales', 0):,.0f}")
61
+ with col2:
62
+ st.metric("Total Revenue", f"₹{sales_summary.get('total_revenue', 0):,.0f}")
63
+ with col3:
64
+ st.metric("Transactions", sales_summary.get('total_transactions', 0))
65
+ with col4:
66
+ st.metric("Avg Sale Value", f"₹{sales_summary.get('avg_sale_value', 0):,.0f}")
67
+ with col5:
68
+ st.metric("Unique Customers", sales_summary.get('unique_customers', 0))
69
+
70
+ # Sales Trend Chart
71
+ st.subheader("📈 Sales Trend")
72
+ sales_trend = get_sales_trend(db, start_date, end_date, report_granularity)
73
+
74
+ if not sales_trend.empty:
75
+ fig = px.line(sales_trend, x='period', y='total_amount',
76
+ title=f'Sales Trend ({report_granularity})',
77
+ labels={'period': 'Period', 'total_amount': 'Sales Amount (₹)'})
78
+ st.plotly_chart(fig, use_container_width=True)
79
+
80
+ # Product Performance
81
+ st.subheader("📦 Product Performance")
82
+ product_performance = get_product_performance(db, start_date, end_date)
83
+
84
+ if not product_performance.empty:
85
+ col1, col2 = st.columns(2)
86
+
87
+ with col1:
88
+ # Top products by revenue
89
+ top_products = product_performance.head(10)
90
+ fig = px.bar(top_products, x='product_name', y='total_revenue',
91
+ title='Top 10 Products by Revenue',
92
+ labels={'product_name': 'Product', 'total_revenue': 'Revenue (₹)'})
93
+ st.plotly_chart(fig, use_container_width=True)
94
+
95
+ with col2:
96
+ # Product sales distribution
97
+ fig = px.pie(product_performance, values='total_quantity', names='product_name',
98
+ title='Product Sales Distribution (Quantity)')
99
+ st.plotly_chart(fig, use_container_width=True)
100
+
101
+ # Detailed product table
102
+ st.dataframe(product_performance, use_container_width=True)
103
+
104
+ # Village-wise Sales
105
+ st.subheader("🗺️ Village-wise Sales Performance")
106
+ village_sales = get_village_sales(db, start_date, end_date)
107
+
108
+ if not village_sales.empty:
109
+ fig = px.bar(village_sales.head(10), x='village', y='total_revenue',
110
+ title='Top 10 Villages by Sales Revenue',
111
+ labels={'village': 'Village', 'total_revenue': 'Revenue (₹)'})
112
+ st.plotly_chart(fig, use_container_width=True)
113
+
114
+ # Village sales table
115
+ st.dataframe(village_sales, use_container_width=True)
116
+
117
+ # Export options
118
+ st.subheader("📤 Export Sales Report")
119
+ col1, col2 = st.columns(2)
120
+
121
+ with col1:
122
+ if st.button("📊 Export Sales Data to CSV"):
123
+ export_sales_data(db, start_date, end_date)
124
+
125
+ with col2:
126
+ if st.button("📈 Generate Sales PDF Report"):
127
+ generate_sales_pdf_report(db, start_date, end_date)
128
+
129
+ except Exception as e:
130
+ st.error(f"Error generating sales reports: {e}")
131
+
132
+ def get_sales_summary(db, start_date, end_date):
133
+ """Get sales summary statistics"""
134
+ try:
135
+ query = '''
136
+ SELECT
137
+ COUNT(*) as total_transactions,
138
+ SUM(total_amount) as total_revenue,
139
+ AVG(total_amount) as avg_sale_value,
140
+ COUNT(DISTINCT customer_id) as unique_customers,
141
+ SUM(total_liters) as total_liters_sold
142
+ FROM sales
143
+ WHERE sale_date BETWEEN ? AND ?
144
+ '''
145
+
146
+ result = db.execute_query(query, (start_date, end_date), log_action=False)
147
+
148
+ if result:
149
+ row = result[0]
150
+ return {
151
+ 'total_transactions': row[0] or 0,
152
+ 'total_revenue': row[1] or 0,
153
+ 'avg_sale_value': row[2] or 0,
154
+ 'unique_customers': row[3] or 0,
155
+ 'total_liters_sold': row[4] or 0
156
+ }
157
+ return {}
158
+
159
+ except Exception as e:
160
+ st.error(f"Error getting sales summary: {e}")
161
+ return {}
162
+
163
+ def get_sales_trend(db, start_date, end_date, granularity):
164
+ """Get sales trend data"""
165
+ try:
166
+ if granularity == "Daily":
167
+ group_by = "DATE(sale_date)"
168
+ period_format = "sale_date"
169
+ elif granularity == "Weekly":
170
+ group_by = "STRFTIME('%Y-%W', sale_date)"
171
+ period_format = "STRFTIME('%Y-W%W', sale_date) as period"
172
+ else: # Monthly
173
+ group_by = "STRFTIME('%Y-%m', sale_date)"
174
+ period_format = "STRFTIME('%Y-%m', sale_date) as period"
175
+
176
+ query = f'''
177
+ SELECT {period_format},
178
+ SUM(total_amount) as total_amount,
179
+ COUNT(*) as transaction_count,
180
+ AVG(total_amount) as avg_amount
181
+ FROM sales
182
+ WHERE sale_date BETWEEN ? AND ?
183
+ GROUP BY {group_by}
184
+ ORDER BY period
185
+ '''
186
+
187
+ return db.get_dataframe('sales', query, params=(start_date, end_date))
188
+
189
+ except Exception as e:
190
+ st.error(f"Error getting sales trend: {e}")
191
+ return pd.DataFrame()
192
+
193
+ def get_product_performance(db, start_date, end_date):
194
+ """Get product performance data"""
195
+ try:
196
+ query = '''
197
+ SELECT
198
+ p.product_name,
199
+ p.packing_type,
200
+ p.capacity_ltr,
201
+ COUNT(si.item_id) as times_sold,
202
+ SUM(si.quantity) as total_quantity,
203
+ SUM(si.amount) as total_revenue,
204
+ AVG(si.rate) as avg_rate
205
+ FROM sale_items si
206
+ JOIN products p ON si.product_id = p.product_id
207
+ JOIN sales s ON si.sale_id = s.sale_id
208
+ WHERE s.sale_date BETWEEN ? AND ?
209
+ GROUP BY p.product_id, p.product_name, p.packing_type, p.capacity_ltr
210
+ ORDER BY total_revenue DESC
211
+ '''
212
+
213
+ return db.get_dataframe('sale_items', query, params=(start_date, end_date))
214
+
215
+ except Exception as e:
216
+ st.error(f"Error getting product performance: {e}")
217
+ return pd.DataFrame()
218
+
219
+ def get_village_sales(db, start_date, end_date):
220
+ """Get village-wise sales data"""
221
+ try:
222
+ query = '''
223
+ SELECT
224
+ c.village,
225
+ COUNT(DISTINCT s.customer_id) as unique_customers,
226
+ COUNT(s.sale_id) as total_transactions,
227
+ SUM(s.total_amount) as total_revenue,
228
+ AVG(s.total_amount) as avg_sale_value
229
+ FROM sales s
230
+ JOIN customers c ON s.customer_id = c.customer_id
231
+ WHERE s.sale_date BETWEEN ? AND ?
232
+ AND c.village IS NOT NULL AND c.village != ''
233
+ GROUP BY c.village
234
+ ORDER BY total_revenue DESC
235
+ '''
236
+
237
+ return db.get_dataframe('sales', query, params=(start_date, end_date))
238
+
239
+ except Exception as e:
240
+ st.error(f"Error getting village sales: {e}")
241
+ return pd.DataFrame()
242
+
243
+ def show_customer_reports_tab(db):
244
+ """Show customer-related reports and analytics"""
245
+ st.subheader("👥 Customer Intelligence Reports")
246
+
247
+ try:
248
+ # Customer Overview
249
+ st.subheader("📋 Customer Overview")
250
+ customer_overview = get_customer_overview(db)
251
+
252
+ if customer_overview:
253
+ col1, col2, col3, col4 = st.columns(4)
254
+
255
+ with col1:
256
+ st.metric("Total Customers", customer_overview.get('total_customers', 0))
257
+ with col2:
258
+ st.metric("Active Customers", customer_overview.get('active_customers', 0))
259
+ with col3:
260
+ st.metric("Avg Customer Value", f"₹{customer_overview.get('avg_customer_value', 0):,.0f}")
261
+ with col4:
262
+ st.metric("Repeat Customer Rate", f"{customer_overview.get('repeat_rate', 0):.1f}%")
263
+
264
+ # Top Customers
265
+ st.subheader("🏆 Top Customers by Spending")
266
+ top_customers = get_top_customers(db)
267
+
268
+ if not top_customers.empty:
269
+ col1, col2 = st.columns(2)
270
+
271
+ with col1:
272
+ fig = px.bar(top_customers.head(10), x='name', y='total_spent',
273
+ title='Top 10 Customers by Total Spending',
274
+ labels={'name': 'Customer', 'total_spent': 'Total Spent (₹)'})
275
+ st.plotly_chart(fig, use_container_width=True)
276
+
277
+ with col2:
278
+ # Customer segmentation by spending
279
+ spending_brackets = categorize_customers_by_spending(top_customers)
280
+ fig = px.pie(values=spending_brackets.values, names=spending_brackets.index,
281
+ title='Customer Segmentation by Spending')
282
+ st.plotly_chart(fig, use_container_width=True)
283
+
284
+ # Customer details table
285
+ st.dataframe(top_customers, use_container_width=True)
286
+
287
+ # Customer Acquisition Trend
288
+ st.subheader("📈 Customer Acquisition Trend")
289
+ acquisition_trend = get_customer_acquisition_trend(db)
290
+
291
+ if not acquisition_trend.empty:
292
+ fig = px.line(acquisition_trend, x='month', y='new_customers',
293
+ title='Monthly Customer Acquisition',
294
+ labels={'month': 'Month', 'new_customers': 'New Customers'})
295
+ st.plotly_chart(fig, use_container_width=True)
296
+
297
+ # Customer Location Analysis
298
+ st.subheader("🗺️ Customer Geographic Distribution")
299
+ customer_geo = get_customer_geographic_data(db)
300
+
301
+ if not customer_geo.empty:
302
+ col1, col2 = st.columns(2)
303
+
304
+ with col1:
305
+ # Village-wise customer count
306
+ village_customers = customer_geo.groupby('village').size().reset_index(name='customer_count')
307
+ village_customers = village_customers.sort_values('customer_count', ascending=False).head(10)
308
+
309
+ fig = px.bar(village_customers, x='village', y='customer_count',
310
+ title='Top 10 Villages by Customer Count',
311
+ labels={'village': 'Village', 'customer_count': 'Number of Customers'})
312
+ st.plotly_chart(fig, use_container_width=True)
313
+
314
+ with col2:
315
+ # Taluka-wise distribution
316
+ taluka_customers = customer_geo.groupby('taluka').size().reset_index(name='customer_count')
317
+
318
+ if not taluka_customers.empty:
319
+ fig = px.pie(taluka_customers, values='customer_count', names='taluka',
320
+ title='Customer Distribution by Taluka')
321
+ st.plotly_chart(fig, use_container_width=True)
322
+
323
+ # Customer Lifetime Value Analysis
324
+ st.subheader("💰 Customer Lifetime Value Analysis")
325
+ clv_data = get_customer_lifetime_value(db)
326
+
327
+ if not clv_data.empty:
328
+ st.dataframe(clv_data, use_container_width=True)
329
+
330
+ except Exception as e:
331
+ st.error(f"Error generating customer reports: {e}")
332
+
333
+ def get_customer_overview(db):
334
+ """Get customer overview statistics"""
335
+ try:
336
+ # Total customers
337
+ total_customers = db.execute_query(
338
+ "SELECT COUNT(*) FROM customers", log_action=False
339
+ )[0][0]
340
+
341
+ # Customers with purchases
342
+ active_customers = db.execute_query(
343
+ "SELECT COUNT(DISTINCT customer_id) FROM sales", log_action=False
344
+ )[0][0]
345
+
346
+ # Average customer value
347
+ avg_value_result = db.execute_query(
348
+ "SELECT AVG(total_amount) FROM sales", log_action=False
349
+ )
350
+ avg_customer_value = avg_value_result[0][0] if avg_value_result else 0
351
+
352
+ # Repeat customer rate
353
+ repeat_customers = db.execute_query(
354
+ "SELECT COUNT(*) FROM (SELECT customer_id FROM sales GROUP BY customer_id HAVING COUNT(*) > 1)",
355
+ log_action=False
356
+ )[0][0]
357
+
358
+ repeat_rate = (repeat_customers / active_customers * 100) if active_customers > 0 else 0
359
+
360
+ return {
361
+ 'total_customers': total_customers,
362
+ 'active_customers': active_customers,
363
+ 'avg_customer_value': avg_customer_value or 0,
364
+ 'repeat_rate': repeat_rate
365
+ }
366
+
367
+ except Exception as e:
368
+ st.error(f"Error getting customer overview: {e}")
369
+ return {}
370
+
371
+ def get_top_customers(db, limit=20):
372
+ """Get top customers by spending"""
373
+ try:
374
+ query = '''
375
+ SELECT
376
+ c.customer_id,
377
+ c.name,
378
+ c.village,
379
+ c.taluka,
380
+ c.mobile,
381
+ COUNT(s.sale_id) as total_purchases,
382
+ SUM(s.total_amount) as total_spent,
383
+ MAX(s.sale_date) as last_purchase_date
384
+ FROM customers c
385
+ JOIN sales s ON c.customer_id = s.customer_id
386
+ GROUP BY c.customer_id, c.name, c.village, c.taluka, c.mobile
387
+ ORDER BY total_spent DESC
388
+ LIMIT ?
389
+ '''
390
+
391
+ return db.get_dataframe('customers', query, params=(limit,))
392
+
393
+ except Exception as e:
394
+ st.error(f"Error getting top customers: {e}")
395
+ return pd.DataFrame()
396
+
397
+ def categorize_customers_by_spending(customers_df):
398
+ """Categorize customers by spending levels"""
399
+ try:
400
+ if customers_df.empty:
401
+ return pd.Series()
402
+
403
+ bins = [0, 1000, 5000, 10000, float('inf')]
404
+ labels = ['Low (<1K)', 'Medium (1K-5K)', 'High (5K-10K)', 'VIP (>10K)']
405
+
406
+ customers_df['spending_category'] = pd.cut(
407
+ customers_df['total_spent'], bins=bins, labels=labels, right=False
408
+ )
409
+
410
+ return customers_df['spending_category'].value_counts()
411
+
412
+ except Exception as e:
413
+ st.error(f"Error categorizing customers: {e}")
414
+ return pd.Series()
415
+
416
+ def get_customer_acquisition_trend(db):
417
+ """Get customer acquisition trend"""
418
+ try:
419
+ query = '''
420
+ SELECT
421
+ STRFTIME('%Y-%m', created_date) as month,
422
+ COUNT(*) as new_customers
423
+ FROM customers
424
+ GROUP BY STRFTIME('%Y-%m', created_date)
425
+ ORDER BY month
426
+ '''
427
+
428
+ return db.get_dataframe('customers', query)
429
+
430
+ except Exception as e:
431
+ st.error(f"Error getting acquisition trend: {e}")
432
+ return pd.DataFrame()
433
+
434
+ def get_customer_geographic_data(db):
435
+ """Get customer geographic distribution"""
436
+ try:
437
+ return db.get_dataframe('customers', '''
438
+ SELECT village, taluka, district, COUNT(*) as customer_count
439
+ FROM customers
440
+ WHERE village IS NOT NULL AND village != ''
441
+ GROUP BY village, taluka, district
442
+ ORDER BY customer_count DESC
443
+ ''')
444
+
445
+ except Exception as e:
446
+ st.error(f"Error getting geographic data: {e}")
447
+ return pd.DataFrame()
448
+
449
+ def get_customer_lifetime_value(db):
450
+ """Calculate customer lifetime value"""
451
+ try:
452
+ query = '''
453
+ SELECT
454
+ c.customer_id,
455
+ c.name,
456
+ c.village,
457
+ COUNT(s.sale_id) as purchase_frequency,
458
+ SUM(s.total_amount) as total_value,
459
+ AVG(s.total_amount) as avg_order_value,
460
+ JULIANDAY(MAX(s.sale_date)) - JULIANDAY(MIN(s.sale_date)) as customer_tenure_days,
461
+ CASE
462
+ WHEN COUNT(s.sale_id) > 0 THEN
463
+ SUM(s.total_amount) / (COUNT(s.sale_id) * GREATEST((JULIANDAY(MAX(s.sale_date)) - JULIANDAY(MIN(s.sale_date)))/30, 1))
464
+ ELSE 0
465
+ END as clv
466
+ FROM customers c
467
+ LEFT JOIN sales s ON c.customer_id = s.customer_id
468
+ GROUP BY c.customer_id, c.name, c.village
469
+ HAVING total_value > 0
470
+ ORDER BY clv DESC
471
+ '''
472
+
473
+ return db.get_dataframe('customers', query)
474
+
475
+ except Exception as e:
476
+ st.error(f"Error calculating CLV: {e}")
477
+ return pd.DataFrame()
478
+
479
+ def show_distributor_reports_tab(db):
480
+ """Show distributor-related reports and analytics"""
481
+ st.subheader("🤝 Distributor Performance Reports")
482
+
483
+ try:
484
+ # Distributor Overview
485
+ st.subheader("📋 Distributor Network Overview")
486
+ distributor_overview = get_distributor_overview(db)
487
+
488
+ if distributor_overview:
489
+ col1, col2, col3, col4 = st.columns(4)
490
+
491
+ with col1:
492
+ st.metric("Total Distributors", distributor_overview.get('total_distributors', 0))
493
+ with col2:
494
+ st.metric("Active Distributors", distributor_overview.get('active_distributors', 0))
495
+ with col3:
496
+ st.metric("Total Sabhasad", distributor_overview.get('total_sabhasad', 0))
497
+ with col4:
498
+ st.metric("Network Size", distributor_overview.get('network_size', 0))
499
+
500
+ # Top Performers
501
+ st.subheader("🏆 Top Performing Distributors")
502
+ top_distributors = get_top_distributors(db)
503
+
504
+ if not top_distributors.empty:
505
+ col1, col2 = st.columns(2)
506
+
507
+ with col1:
508
+ fig = px.bar(top_distributors.head(10), x='name', y='sabhasad_count',
509
+ title='Top 10 Distributors by Sabhasad Count',
510
+ labels={'name': 'Distributor', 'sabhasad_count': 'Sabhasad Count'})
511
+ st.plotly_chart(fig, use_container_width=True)
512
+
513
+ with col2:
514
+ # Performance tiers
515
+ tier_distribution = top_distributors['performance_tier'].value_counts()
516
+ fig = px.pie(values=tier_distribution.values, names=tier_distribution.index,
517
+ title='Distributor Performance Tier Distribution')
518
+ st.plotly_chart(fig, use_container_width=True)
519
+
520
+ # Distributor details table
521
+ st.dataframe(top_distributors, use_container_width=True)
522
+
523
+ # Territory Coverage
524
+ st.subheader("🗺️ Territory Coverage Analysis")
525
+ territory_coverage = get_territory_coverage(db)
526
+
527
+ if not territory_coverage.empty:
528
+ col1, col2 = st.columns(2)
529
+
530
+ with col1:
531
+ fig = px.bar(territory_coverage.head(10), x='village', y='distributor_count',
532
+ title='Villages with Multiple Distributors',
533
+ labels={'village': 'Village', 'distributor_count': 'Distributor Count'})
534
+ st.plotly_chart(fig, use_container_width=True)
535
+
536
+ with col2:
537
+ # Coverage status
538
+ coverage_status = {
539
+ 'Covered Villages': len(territory_coverage[territory_coverage['distributor_count'] > 0]),
540
+ 'Uncovered Villages': len(territory_coverage[territory_coverage['distributor_count'] == 0])
541
+ }
542
+
543
+ fig = px.pie(values=coverage_status.values(), names=coverage_status.keys(),
544
+ title='Village Coverage Status')
545
+ st.plotly_chart(fig, use_container_width=True)
546
+
547
+ # Growth Potential Analysis
548
+ st.subheader("📈 Growth Potential Analysis")
549
+ growth_potential = get_growth_potential(db)
550
+
551
+ if not growth_potential.empty:
552
+ st.dataframe(growth_potential, use_container_width=True)
553
+
554
+ except Exception as e:
555
+ st.error(f"Error generating distributor reports: {e}")
556
+
557
+ def get_distributor_overview(db):
558
+ """Get distributor network overview"""
559
+ try:
560
+ distributors = db.get_dataframe('distributors', 'SELECT * FROM distributors')
561
+
562
+ if distributors.empty:
563
+ return {}
564
+
565
+ total_distributors = len(distributors)
566
+ active_distributors = len(distributors[distributors['sabhasad_count'] > 0])
567
+ total_sabhasad = distributors['sabhasad_count'].sum()
568
+ network_size = total_distributors + total_sabhasad
569
+
570
+ return {
571
+ 'total_distributors': total_distributors,
572
+ 'active_distributors': active_distributors,
573
+ 'total_sabhasad': total_sabhasad,
574
+ 'network_size': network_size
575
+ }
576
+
577
+ except Exception as e:
578
+ st.error(f"Error getting distributor overview: {e}")
579
+ return {}
580
+
581
+ def get_top_distributors(db, limit=20):
582
+ """Get top performing distributors"""
583
+ try:
584
+ distributors = db.get_dataframe('distributors', '''
585
+ SELECT *,
586
+ (sabhasad_count + contact_in_group) as network_score
587
+ FROM distributors
588
+ ORDER BY sabhasad_count DESC
589
+ LIMIT ?
590
+ ''', params=(limit,))
591
+
592
+ if not distributors.empty:
593
+ # Add performance tier
594
+ distributors['performance_tier'] = distributors['sabhasad_count'].apply(
595
+ lambda x: 'Platinum' if x >= 20 else 'Gold' if x >= 10 else 'Silver' if x >= 5 else 'Bronze'
596
+ )
597
+
598
+ return distributors
599
+
600
+ except Exception as e:
601
+ st.error(f"Error getting top distributors: {e}")
602
+ return pd.DataFrame()
603
+
604
+ def get_territory_coverage(db):
605
+ """Get territory coverage analysis"""
606
+ try:
607
+ # Get all villages from customers
608
+ customer_villages = db.get_dataframe('customers', '''
609
+ SELECT DISTINCT village
610
+ FROM customers
611
+ WHERE village IS NOT NULL AND village != ''
612
+ ''')
613
+
614
+ # Get distributor villages
615
+ distributor_villages = db.get_dataframe('distributors', '''
616
+ SELECT village, COUNT(*) as distributor_count
617
+ FROM distributors
618
+ WHERE village IS NOT NULL AND village != ''
619
+ GROUP BY village
620
+ ''')
621
+
622
+ # Merge to see coverage
623
+ if not customer_villages.empty:
624
+ if not distributor_villages.empty:
625
+ coverage = pd.merge(customer_villages, distributor_villages, on='village', how='left')
626
+ else:
627
+ coverage = customer_villages.copy()
628
+ coverage['distributor_count'] = 0
629
+
630
+ coverage['distributor_count'] = coverage['distributor_count'].fillna(0)
631
+ return coverage.sort_values('distributor_count', ascending=False)
632
+
633
+ return pd.DataFrame()
634
+
635
+ except Exception as e:
636
+ st.error(f"Error getting territory coverage: {e}")
637
+ return pd.DataFrame()
638
+
639
+ def get_growth_potential(db):
640
+ """Get distributor growth potential analysis"""
641
+ try:
642
+ return db.get_dataframe('distributors', '''
643
+ SELECT
644
+ name,
645
+ village,
646
+ sabhasad_count,
647
+ contact_in_group,
648
+ (contact_in_group - sabhasad_count) as conversion_potential,
649
+ CASE
650
+ WHEN sabhasad_count = 0 THEN contact_in_group * 0.3
651
+ ELSE (contact_in_group / sabhasad_count - 1) * sabhasad_count
652
+ END as growth_opportunity
653
+ FROM distributors
654
+ WHERE contact_in_group > sabhasad_count
655
+ ORDER BY growth_opportunity DESC
656
+ ''')
657
+
658
+ except Exception as e:
659
+ st.error(f"Error getting growth potential: {e}")
660
+ return pd.DataFrame()
661
+
662
+ def show_financial_reports_tab(db):
663
+ """Show financial reports and analytics"""
664
+ st.subheader("💰 Financial Performance Reports")
665
+
666
+ # Date range selection
667
+ col1, col2 = st.columns(2)
668
+ with col1:
669
+ start_date = st.date_input("Start Date", datetime.now() - timedelta(days=90), key="financial_start")
670
+ with col2:
671
+ end_date = st.date_input("End Date", datetime.now(), key="financial_end")
672
+
673
+ try:
674
+ # Financial Summary
675
+ st.subheader("📋 Financial Summary")
676
+ financial_summary = get_financial_summary(db, start_date, end_date)
677
+
678
+ if financial_summary:
679
+ col1, col2, col3, col4 = st.columns(4)
680
+
681
+ with col1:
682
+ st.metric("Total Revenue", f"₹{financial_summary.get('total_revenue', 0):,.0f}")
683
+ with col2:
684
+ st.metric("Total Payments", f"₹{financial_summary.get('total_payments', 0):,.0f}")
685
+ with col3:
686
+ st.metric("Pending Amount", f"₹{financial_summary.get('pending_amount', 0):,.0f}")
687
+ with col4:
688
+ collection_rate = financial_summary.get('collection_rate', 0)
689
+ st.metric("Collection Rate", f"{collection_rate:.1f}%")
690
+
691
+ # Revenue vs Payments Trend
692
+ st.subheader("📈 Revenue vs Payments Trend")
693
+ financial_trend = get_financial_trend(db, start_date, end_date)
694
+
695
+ if not financial_trend.empty:
696
+ fig = go.Figure()
697
+ fig.add_trace(go.Scatter(x=financial_trend['period'], y=financial_trend['revenue'],
698
+ name='Revenue', line=dict(color='green')))
699
+ fig.add_trace(go.Scatter(x=financial_trend['period'], y=financial_trend['payments'],
700
+ name='Payments', line=dict(color='blue')))
701
+ fig.update_layout(title='Revenue vs Payments Trend', xaxis_title='Period', yaxis_title='Amount (₹)')
702
+ st.plotly_chart(fig, use_container_width=True)
703
+
704
+ # Payment Method Analysis
705
+ st.subheader("💳 Payment Method Analysis")
706
+ payment_methods = get_payment_methods_analysis(db, start_date, end_date)
707
+
708
+ if not payment_methods.empty:
709
+ col1, col2 = st.columns(2)
710
+
711
+ with col1:
712
+ fig = px.pie(payment_methods, values='total_amount', names='payment_method',
713
+ title='Payment Methods Distribution')
714
+ st.plotly_chart(fig, use_container_width=True)
715
+
716
+ with col2:
717
+ fig = px.bar(payment_methods, x='payment_method', y='transaction_count',
718
+ title='Transactions by Payment Method',
719
+ labels={'payment_method': 'Payment Method', 'transaction_count': 'Number of Transactions'})
720
+ st.plotly_chart(fig, use_container_width=True)
721
+
722
+ # Aging Analysis
723
+ st.subheader("⏳ Accounts Receivable Aging")
724
+ aging_analysis = get_aging_analysis(db)
725
+
726
+ if not aging_analysis.empty:
727
+ st.dataframe(aging_analysis, use_container_width=True)
728
+
729
+ except Exception as e:
730
+ st.error(f"Error generating financial reports: {e}")
731
+
732
+ def get_financial_summary(db, start_date, end_date):
733
+ """Get financial summary"""
734
+ try:
735
+ # Total revenue
736
+ revenue_result = db.execute_query(
737
+ "SELECT SUM(total_amount) FROM sales WHERE sale_date BETWEEN ? AND ?",
738
+ (start_date, end_date), log_action=False
739
+ )
740
+ total_revenue = revenue_result[0][0] or 0 if revenue_result else 0
741
+
742
+ # Total payments
743
+ payments_result = db.execute_query(
744
+ "SELECT SUM(amount) FROM payments WHERE payment_date BETWEEN ? AND ? AND status = 'Completed'",
745
+ (start_date, end_date), log_action=False
746
+ )
747
+ total_payments = payments_result[0][0] or 0 if payments_result else 0
748
+
749
+ # Pending amount
750
+ pending_result = db.execute_query(
751
+ "SELECT SUM(total_amount - COALESCE((SELECT SUM(amount) FROM payments WHERE payments.sale_id = sales.sale_id AND status = 'Completed'), 0)) FROM sales WHERE sale_date BETWEEN ? AND ?",
752
+ (start_date, end_date), log_action=False
753
+ )
754
+ pending_amount = pending_result[0][0] or 0 if pending_result else 0
755
+
756
+ # Collection rate
757
+ collection_rate = (total_payments / total_revenue * 100) if total_revenue > 0 else 0
758
+
759
+ return {
760
+ 'total_revenue': total_revenue,
761
+ 'total_payments': total_payments,
762
+ 'pending_amount': pending_amount,
763
+ 'collection_rate': collection_rate
764
+ }
765
+
766
+ except Exception as e:
767
+ st.error(f"Error getting financial summary: {e}")
768
+ return {}
769
+
770
+ def get_financial_trend(db, start_date, end_date):
771
+ """Get financial trend data"""
772
+ try:
773
+ query = '''
774
+ SELECT
775
+ STRFTIME('%Y-%m', sale_date) as period,
776
+ SUM(total_amount) as revenue,
777
+ (SELECT SUM(amount) FROM payments
778
+ WHERE STRFTIME('%Y-%m', payment_date) = STRFTIME('%Y-%m', sales.sale_date)
779
+ AND status = 'Completed') as payments
780
+ FROM sales
781
+ WHERE sale_date BETWEEN ? AND ?
782
+ GROUP BY STRFTIME('%Y-%m', sale_date)
783
+ ORDER BY period
784
+ '''
785
+
786
+ return db.get_dataframe('sales', query, params=(start_date, end_date))
787
+
788
+ except Exception as e:
789
+ st.error(f"Error getting financial trend: {e}")
790
+ return pd.DataFrame()
791
+
792
+ def get_payment_methods_analysis(db, start_date, end_date):
793
+ """Get payment methods analysis"""
794
+ try:
795
+ query = '''
796
+ SELECT
797
+ payment_method,
798
+ COUNT(*) as transaction_count,
799
+ SUM(amount) as total_amount,
800
+ AVG(amount) as avg_amount
801
+ FROM payments
802
+ WHERE payment_date BETWEEN ? AND ? AND status = 'Completed'
803
+ GROUP BY payment_method
804
+ ORDER BY total_amount DESC
805
+ '''
806
+
807
+ return db.get_dataframe('payments', query, params=(start_date, end_date))
808
+
809
+ except Exception as e:
810
+ st.error(f"Error getting payment methods analysis: {e}")
811
+ return pd.DataFrame()
812
+
813
+ def get_aging_analysis(db):
814
+ """Get accounts receivable aging analysis"""
815
+ try:
816
+ query = '''
817
+ SELECT
818
+ s.invoice_no,
819
+ c.name as customer_name,
820
+ c.village,
821
+ s.sale_date,
822
+ s.total_amount,
823
+ COALESCE(SUM(p.amount), 0) as paid_amount,
824
+ (s.total_amount - COALESCE(SUM(p.amount), 0)) as pending_amount,
825
+ JULIANDAY('now') - JULIANDAY(s.sale_date) as days_pending,
826
+ CASE
827
+ WHEN JULIANDAY('now') - JULIANDAY(s.sale_date) <= 30 THEN '0-30 days'
828
+ WHEN JULIANDAY('now') - JULIANDAY(s.sale_date) <= 60 THEN '31-60 days'
829
+ WHEN JULIANDAY('now') - JULIANDAY(s.sale_date) <= 90 THEN '61-90 days'
830
+ ELSE 'Over 90 days'
831
+ END as aging_bucket
832
+ FROM sales s
833
+ JOIN customers c ON s.customer_id = c.customer_id
834
+ LEFT JOIN payments p ON s.sale_id = p.sale_id AND p.status = 'Completed'
835
+ WHERE s.payment_status IN ('Pending', 'Partial')
836
+ GROUP BY s.sale_id, s.invoice_no, c.name, c.village, s.sale_date, s.total_amount
837
+ HAVING pending_amount > 0
838
+ ORDER BY days_pending DESC
839
+ '''
840
+
841
+ return db.get_dataframe('sales', query)
842
+
843
+ except Exception as e:
844
+ st.error(f"Error getting aging analysis: {e}")
845
+ return pd.DataFrame()
846
+
847
+ def show_performance_reports_tab(db):
848
+ """Show overall performance and KPI reports"""
849
+ st.subheader("🎯 Business Performance Dashboard")
850
+
851
+ try:
852
+ # Key Performance Indicators
853
+ st.subheader("📊 Key Performance Indicators (KPIs)")
854
+
855
+ kpis = get_performance_kpis(db)
856
+
857
+ if kpis:
858
+ col1, col2, col3, col4 = st.columns(4)
859
+
860
+ with col1:
861
+ st.metric("Monthly Revenue", f"₹{kpis.get('monthly_revenue', 0):,.0f}")
862
+ with col2:
863
+ st.metric("Customer Growth", f"+{kpis.get('customer_growth', 0)}")
864
+ with col3:
865
+ st.metric("Demo Conversion", f"{kpis.get('demo_conversion_rate', 0):.1f}%")
866
+ with col4:
867
+ st.metric("Payment Collection", f"{kpis.get('collection_rate', 0):.1f}%")
868
+
869
+ # Performance Scorecard
870
+ st.subheader("📋 Performance Scorecard")
871
+ scorecard = get_performance_scorecard(db)
872
+
873
+ if not scorecard.empty:
874
+ st.dataframe(scorecard, use_container_width=True)
875
+
876
+ # Goal Tracking
877
+ st.subheader("🎯 Goal vs Actual Performance")
878
+ goals_vs_actual = get_goals_vs_actual(db)
879
+
880
+ if not goals_vs_actual.empty:
881
+ for _, goal in goals_vs_actual.iterrows():
882
+ progress = min((goal['actual'] / goal['target']) * 100, 100) if goal['target'] > 0 else 0
883
+ st.write(f"**{goal['metric']}**")
884
+ st.progress(progress / 100)
885
+ st.write(f"Target: {goal['target']} | Actual: {goal['actual']} | Progress: {progress:.1f}%")
886
+
887
+ # Export Comprehensive Report
888
+ st.subheader("📤 Export Comprehensive Report")
889
+
890
+ if st.button("📄 Generate Full Business Report"):
891
+ generate_comprehensive_report(db)
892
+
893
+ except Exception as e:
894
+ st.error(f"Error generating performance reports: {e}")
895
+
896
+ def get_performance_kpis(db):
897
+ """Get key performance indicators"""
898
+ try:
899
+ # Monthly revenue (last 30 days)
900
+ monthly_revenue_result = db.execute_query(
901
+ "SELECT SUM(total_amount) FROM sales WHERE sale_date >= date('now', '-30 days')",
902
+ log_action=False
903
+ )
904
+ monthly_revenue = monthly_revenue_result[0][0] or 0 if monthly_revenue_result else 0
905
+
906
+ # Customer growth (last 30 days)
907
+ customer_growth_result = db.execute_query(
908
+ "SELECT COUNT(*) FROM customers WHERE created_date >= date('now', '-30 days')",
909
+ log_action=False
910
+ )
911
+ customer_growth = customer_growth_result[0][0] or 0 if customer_growth_result else 0
912
+
913
+ # Demo conversion rate
914
+ demos_result = db.execute_query(
915
+ "SELECT COUNT(*), SUM(CASE WHEN conversion_status = 'Converted' THEN 1 ELSE 0 END) FROM demos",
916
+ log_action=False
917
+ )
918
+ if demos_result and demos_result[0][0] > 0:
919
+ demo_conversion_rate = (demos_result[0][1] / demos_result[0][0]) * 100
920
+ else:
921
+ demo_conversion_rate = 0
922
+
923
+ # Collection rate
924
+ collection_result = db.execute_query(
925
+ "SELECT SUM(total_amount), SUM(COALESCE((SELECT SUM(amount) FROM payments WHERE payments.sale_id = sales.sale_id AND status = 'Completed'), 0)) FROM sales",
926
+ log_action=False
927
+ )
928
+ if collection_result and collection_result[0][0] and collection_result[0][0] > 0:
929
+ collection_rate = (collection_result[0][1] / collection_result[0][0]) * 100
930
+ else:
931
+ collection_rate = 0
932
+
933
+ return {
934
+ 'monthly_revenue': monthly_revenue,
935
+ 'customer_growth': customer_growth,
936
+ 'demo_conversion_rate': demo_conversion_rate,
937
+ 'collection_rate': collection_rate
938
+ }
939
+
940
+ except Exception as e:
941
+ st.error(f"Error getting KPIs: {e}")
942
+ return {}
943
+
944
+ def get_performance_scorecard(db):
945
+ """Get performance scorecard"""
946
+ try:
947
+ scorecard_data = []
948
+
949
+ # Sales performance
950
+ sales_data = db.execute_query(
951
+ "SELECT COUNT(*), SUM(total_amount), AVG(total_amount) FROM sales WHERE sale_date >= date('now', '-30 days')",
952
+ log_action=False
953
+ )
954
+ if sales_data:
955
+ scorecard_data.append({
956
+ 'Category': 'Sales',
957
+ 'Metric': 'Monthly Transactions',
958
+ 'Value': sales_data[0][0] or 0,
959
+ 'Target': 50,
960
+ 'Status': 'On Track' if (sales_data[0][0] or 0) >= 40 else 'Needs Attention'
961
+ })
962
+
963
+ scorecard_data.append({
964
+ 'Category': 'Sales',
965
+ 'Metric': 'Monthly Revenue',
966
+ 'Value': f"₹{sales_data[0][1] or 0:,.0f}",
967
+ 'Target': '₹50,000',
968
+ 'Status': 'On Track' if (sales_data[0][1] or 0) >= 40000 else 'Needs Attention'
969
+ })
970
+
971
+ # Customer performance
972
+ customer_data = db.execute_query(
973
+ "SELECT COUNT(*) FROM customers WHERE created_date >= date('now', '-30 days')",
974
+ log_action=False
975
+ )
976
+ if customer_data:
977
+ scorecard_data.append({
978
+ 'Category': 'Customers',
979
+ 'Metric': 'New Customers',
980
+ 'Value': customer_data[0][0] or 0,
981
+ 'Target': 20,
982
+ 'Status': 'On Track' if (customer_data[0][0] or 0) >= 15 else 'Needs Attention'
983
+ })
984
+
985
+ # Distributor performance
986
+ distributor_data = db.execute_query(
987
+ "SELECT COUNT(*), SUM(sabhasad_count) FROM distributors",
988
+ log_action=False
989
+ )
990
+ if distributor_data:
991
+ scorecard_data.append({
992
+ 'Category': 'Distribution',
993
+ 'Metric': 'Total Distributors',
994
+ 'Value': distributor_data[0][0] or 0,
995
+ 'Target': 10,
996
+ 'Status': 'On Track' if (distributor_data[0][0] or 0) >= 8 else 'Needs Attention'
997
+ })
998
+
999
+ scorecard_data.append({
1000
+ 'Category': 'Distribution',
1001
+ 'Metric': 'Total Sabhasad',
1002
+ 'Value': distributor_data[0][1] or 0,
1003
+ 'Target': 100,
1004
+ 'Status': 'On Track' if (distributor_data[0][1] or 0) >= 80 else 'Needs Attention'
1005
+ })
1006
+
1007
+ return pd.DataFrame(scorecard_data)
1008
+
1009
+ except Exception as e:
1010
+ st.error(f"Error getting performance scorecard: {e}")
1011
+ return pd.DataFrame()
1012
+
1013
+ def get_goals_vs_actual(db):
1014
+ """Get goals vs actual performance"""
1015
+ try:
1016
+ goals = [
1017
+ {'metric': 'Monthly Revenue', 'target': 50000, 'actual': 0},
1018
+ {'metric': 'New Customers', 'target': 20, 'actual': 0},
1019
+ {'metric': 'Demos Conducted', 'target': 15, 'actual': 0},
1020
+ {'metric': 'Payment Collection', 'target': 95, 'actual': 0}
1021
+ ]
1022
+
1023
+ # Get actual values
1024
+ revenue_result = db.execute_query(
1025
+ "SELECT SUM(total_amount) FROM sales WHERE sale_date >= date('now', '-30 days')",
1026
+ log_action=False
1027
+ )
1028
+ if revenue_result:
1029
+ goals[0]['actual'] = revenue_result[0][0] or 0
1030
+
1031
+ customer_result = db.execute_query(
1032
+ "SELECT COUNT(*) FROM customers WHERE created_date >= date('now', '-30 days')",
1033
+ log_action=False
1034
+ )
1035
+ if customer_result:
1036
+ goals[1]['actual'] = customer_result[0][0] or 0
1037
+
1038
+ demo_result = db.execute_query(
1039
+ "SELECT COUNT(*) FROM demos WHERE demo_date >= date('now', '-30 days')",
1040
+ log_action=False
1041
+ )
1042
+ if demo_result:
1043
+ goals[2]['actual'] = demo_result[0][0] or 0
1044
+
1045
+ collection_result = db.execute_query(
1046
+ "SELECT SUM(total_amount), SUM(COALESCE((SELECT SUM(amount) FROM payments WHERE payments.sale_id = sales.sale_id AND status = 'Completed'), 0)) FROM sales WHERE sale_date >= date('now', '-30 days')",
1047
+ log_action=False
1048
+ )
1049
+ if collection_result and collection_result[0][0] and collection_result[0][0] > 0:
1050
+ goals[3]['actual'] = (collection_result[0][1] / collection_result[0][0]) * 100
1051
+
1052
+ return pd.DataFrame(goals)
1053
+
1054
+ except Exception as e:
1055
+ st.error(f"Error getting goals vs actual: {e}")
1056
+ return pd.DataFrame()
1057
+
1058
+ def export_sales_data(db, start_date, end_date):
1059
+ """Export sales data to CSV"""
1060
+ try:
1061
+ sales_data = db.get_dataframe('sales', '''
1062
+ SELECT s.*, c.name as customer_name, c.village, c.taluka
1063
+ FROM sales s
1064
+ JOIN customers c ON s.customer_id = c.customer_id
1065
+ WHERE s.sale_date BETWEEN ? AND ?
1066
+ ORDER BY s.sale_date DESC
1067
+ ''', params=(start_date, end_date))
1068
+
1069
+ if not sales_data.empty:
1070
+ csv = sales_data.to_csv(index=False)
1071
+ st.download_button(
1072
+ label="📥 Download Sales Data as CSV",
1073
+ data=csv,
1074
+ file_name=f"sales_report_{datetime.now().strftime('%Y%m%d')}.csv",
1075
+ mime="text/csv"
1076
+ )
1077
+
1078
+ except Exception as e:
1079
+ st.error(f"Error exporting sales data: {e}")
1080
+
1081
+ def generate_sales_pdf_report(db, start_date, end_date):
1082
+ """Generate PDF sales report (placeholder)"""
1083
+ st.info("📄 PDF report generation feature will be implemented soon!")
1084
+ st.info("This would generate a comprehensive PDF report with charts and analysis.")
1085
+
1086
+ def generate_comprehensive_report(db):
1087
+ """Generate comprehensive business report"""
1088
+ st.success("🎉 Comprehensive business report generated!")
1089
+ st.info("""
1090
+ **Report Includes:**
1091
+ - Executive Summary
1092
+ - Sales Performance Analysis
1093
+ - Customer Insights
1094
+ - Distributor Network Performance
1095
+ - Financial Overview
1096
+ - Key Recommendations
1097
+
1098
+ *Note: Full report generation with export features will be implemented in the next version.*
1099
+ """)
1100
+
1101
+ # Add this to your main file routing
pages/sales.py ADDED
@@ -0,0 +1,500 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # pages/sales.py
2
+ import streamlit as st
3
+ import pandas as pd
4
+ from datetime import datetime, timedelta
5
+ import re
6
+
7
+ def show_sales_page(db, whatsapp_manager=None):
8
+ """Show enhanced sales management page with quick customer creation and WhatsApp"""
9
+ st.title("💰 Sales Management")
10
+
11
+ if not db:
12
+ st.error("Database not available. Please check initialization.")
13
+ return
14
+
15
+ # Tabs for different sales functions
16
+ tab1, tab2, tab3 = st.tabs(["➕ Quick Sale", "📋 Sales History", "🔍 Sales Analytics"])
17
+
18
+ with tab1:
19
+ show_quick_sale_tab(db, whatsapp_manager)
20
+
21
+ with tab2:
22
+ show_sales_history_tab(db)
23
+
24
+ with tab3:
25
+ show_sales_analytics_tab(db)
26
+
27
+ def show_quick_sale_tab(db, whatsapp_manager):
28
+ """Show tab for quick sales with instant customer creation"""
29
+ st.subheader("🚀 Quick Sale - Create Customer & Sale in One Go")
30
+
31
+ with st.form("quick_sale_form"):
32
+ # Customer Section - Quick Create
33
+ st.markdown("### 👥 Customer Information")
34
+
35
+ col1, col2 = st.columns(2)
36
+
37
+ with col1:
38
+ # Option 1: Select existing customer
39
+ st.write("**Select Existing Customer**")
40
+ customers = db.get_dataframe('customers')
41
+ existing_customer_options = {f"{row['name']} ({row['mobile']})": row['customer_id']
42
+ for _, row in customers.iterrows()} if not customers.empty else {}
43
+
44
+ use_existing = st.selectbox("Choose Customer",
45
+ options=[""] + list(existing_customer_options.keys()),
46
+ key="existing_customer")
47
+
48
+ if use_existing:
49
+ customer_id = existing_customer_options[use_existing]
50
+ # Get customer details for display
51
+ customer_details = customers[customers['customer_id'] == customer_id].iloc[0]
52
+ st.success(f"Selected: {customer_details['name']} - {customer_details['mobile']}")
53
+ else:
54
+ customer_id = None
55
+
56
+ with col2:
57
+ # Option 2: Create new customer instantly
58
+ st.write("**Or Create New Customer**")
59
+ new_customer_name = st.text_input("Customer Name*", placeholder="Enter customer name")
60
+ new_customer_mobile = st.text_input("Mobile Number", placeholder="Enter mobile number")
61
+ new_customer_village = st.text_input("Village", placeholder="Enter village")
62
+
63
+ if new_customer_name:
64
+ # Check if customer already exists
65
+ existing_customer = db.execute_query(
66
+ "SELECT customer_id FROM customers WHERE name = ? OR mobile = ?",
67
+ (new_customer_name, new_customer_mobile),
68
+ log_action=False
69
+ )
70
+
71
+ if existing_customer:
72
+ st.warning("⚠️ Customer with same name/mobile already exists!")
73
+ customer_id = existing_customer[0][0]
74
+ else:
75
+ customer_id = None # Will create during sale submission
76
+
77
+ # Sale Information
78
+ st.markdown("### 📄 Sale Information")
79
+ col1, col2 = st.columns(2)
80
+
81
+ with col1:
82
+ invoice_no = st.text_input("Invoice Number*", value=db.generate_invoice_number())
83
+ with col2:
84
+ sale_date = st.date_input("Sale Date", datetime.now())
85
+
86
+ # Products Section with Smart Pricing
87
+ st.markdown("### 📦 Add Products")
88
+
89
+ products = db.get_dataframe('products')
90
+ if products.empty:
91
+ st.error("❌ No products found in database. Please add products first.")
92
+ return
93
+
94
+ product_options = {row['product_name']: {
95
+ 'product_id': row['product_id'],
96
+ 'standard_rate': row['standard_rate'],
97
+ 'packing_type': row['packing_type'],
98
+ 'capacity_ltr': row['capacity_ltr']
99
+ } for _, row in products.iterrows()}
100
+
101
+ sale_items = []
102
+ total_amount = 0
103
+
104
+ # Create 3 product rows
105
+ for i in range(3):
106
+ st.markdown(f"**Product {i+1}**")
107
+ col1, col2, col3, col4, col5 = st.columns([3, 1, 2, 2, 1])
108
+
109
+ with col1:
110
+ selected_product = st.selectbox(f"Select Product",
111
+ options=[""] + list(product_options.keys()),
112
+ key=f"product_{i}")
113
+
114
+ with col2:
115
+ quantity = st.number_input(f"Qty", min_value=0, value=0, key=f"qty_{i}")
116
+
117
+ with col3:
118
+ if selected_product:
119
+ default_rate = product_options[selected_product]['standard_rate']
120
+ rate = st.number_input(f"Rate (₹)", min_value=0.0, value=float(default_rate),
121
+ step=1.0, key=f"rate_{i}")
122
+ # Show discount indicator if rate is changed
123
+ if rate < default_rate:
124
+ discount = ((default_rate - rate) / default_rate) * 100
125
+ st.info(f"🎯 {discount:.1f}% off")
126
+ else:
127
+ rate = st.number_input(f"Rate (₹)", min_value=0.0, value=0.0, key=f"rate_{i}")
128
+
129
+ with col4:
130
+ if selected_product and quantity > 0:
131
+ amount = quantity * rate
132
+ st.metric("Amount", f"₹{amount:,.2f}")
133
+ else:
134
+ amount = 0
135
+ st.metric("Amount", "₹0")
136
+
137
+ with col5:
138
+ if selected_product and quantity > 0:
139
+ product_info = product_options[selected_product]
140
+ st.write(f"*{product_info['packing_type']}*")
141
+ if product_info['capacity_ltr'] > 0:
142
+ st.write(f"{product_info['capacity_ltr']}L")
143
+
144
+ if selected_product and quantity > 0:
145
+ sale_items.append({
146
+ 'product_id': product_options[selected_product]['product_id'],
147
+ 'product_name': selected_product,
148
+ 'quantity': quantity,
149
+ 'rate': rate,
150
+ 'amount': amount,
151
+ 'standard_rate': product_options[selected_product]['standard_rate']
152
+ })
153
+ total_amount += amount
154
+
155
+ # Show running total
156
+ if total_amount > 0:
157
+ st.success(f"### 🎯 Running Total: ₹{total_amount:,.2f}")
158
+
159
+ # Additional Options
160
+ st.markdown("### ⚙️ Additional Options")
161
+
162
+ col1, col2 = st.columns(2)
163
+
164
+ with col1:
165
+ notes = st.text_area("Sale Notes", placeholder="Any special notes about this sale...")
166
+
167
+ with col2:
168
+ # WhatsApp Notification Options
169
+ st.write("**📱 Customer Notification**")
170
+ send_whatsapp = st.checkbox("Send WhatsApp Notification", value=True)
171
+ if send_whatsapp and not whatsapp_manager:
172
+ st.warning("WhatsApp manager not available")
173
+
174
+ # Payment options
175
+ payment_received = st.checkbox("Payment Received", value=False)
176
+ if payment_received:
177
+ payment_amount = st.number_input("Payment Amount", min_value=0.0, value=float(total_amount))
178
+ payment_method = st.selectbox("Payment Method", ["Cash", "G-Pay", "Cheque", "Bank Transfer"])
179
+
180
+ # Submit Section
181
+ st.markdown("---")
182
+ submitted = st.form_submit_button("🚀 Create Sale & Notify Customer", type="primary")
183
+
184
+ if submitted:
185
+ # Validation
186
+ errors = []
187
+
188
+ if not customer_id and not new_customer_name:
189
+ errors.append("Please select a customer or enter new customer name")
190
+ if not invoice_no:
191
+ errors.append("Invoice number is required")
192
+ if not sale_items:
193
+ errors.append("Please add at least one product")
194
+
195
+ if errors:
196
+ for error in errors:
197
+ st.error(error)
198
+ else:
199
+ try:
200
+ # Create customer if new
201
+ if not customer_id and new_customer_name:
202
+ customer_id = db.add_customer(
203
+ name=new_customer_name,
204
+ mobile=new_customer_mobile,
205
+ village=new_customer_village
206
+ )
207
+ if customer_id and customer_id > 0:
208
+ st.success(f"✅ New customer created: {new_customer_name}")
209
+
210
+ if customer_id:
211
+ # Create sale
212
+ sale_id = db.add_sale(
213
+ invoice_no=invoice_no,
214
+ customer_id=customer_id,
215
+ sale_date=sale_date,
216
+ items=sale_items,
217
+ notes=notes
218
+ )
219
+
220
+ if sale_id and sale_id > 0:
221
+ st.success(f"✅ Sale created successfully! Sale ID: {sale_id}")
222
+
223
+ # Add payment if received
224
+ if payment_received:
225
+ db.execute_query('''
226
+ INSERT INTO payments (sale_id, payment_date, payment_method, amount)
227
+ VALUES (?, ?, ?, ?)
228
+ ''', (sale_id, sale_date, payment_method, payment_amount))
229
+ st.success(f"✅ Payment recorded: ₹{payment_amount:,.2f}")
230
+
231
+ # Send WhatsApp notification
232
+ if send_whatsapp and whatsapp_manager:
233
+ send_sale_notification(whatsapp_manager, db, sale_id, customer_id)
234
+
235
+ # Show sale summary
236
+ show_quick_sale_summary(db, sale_id, sale_items, customer_id)
237
+
238
+ # Clear form for next sale
239
+ if st.button("🔄 Create Another Sale"):
240
+ st.rerun()
241
+ else:
242
+ st.error("❌ Failed to create sale.")
243
+
244
+ except Exception as e:
245
+ st.error(f"Error creating sale: {e}")
246
+
247
+ def send_sale_notification(whatsapp_manager, db, sale_id, customer_id):
248
+ """Send WhatsApp notification to customer about their sale"""
249
+ try:
250
+ # Get sale and customer details
251
+ sale_details = db.get_dataframe('sales', f"SELECT * FROM sales WHERE sale_id = {sale_id}")
252
+ customer_details = db.get_dataframe('customers', f"SELECT * FROM customers WHERE customer_id = {customer_id}")
253
+
254
+ if sale_details.empty or customer_details.empty:
255
+ return False
256
+
257
+ sale = sale_details.iloc[0]
258
+ customer = customer_details.iloc[0]
259
+
260
+ # Get sale items
261
+ items_details = db.get_dataframe('sale_items', f'''
262
+ SELECT si.*, p.product_name
263
+ FROM sale_items si
264
+ JOIN products p ON si.product_id = p.product_id
265
+ WHERE si.sale_id = {sale_id}
266
+ ''')
267
+
268
+ if customer.get('mobile'):
269
+ # Create notification message
270
+ message = f"""Hello {customer['name']}! 🎉
271
+
272
+ Thank you for your purchase!
273
+
274
+ 📄 Invoice: {sale['invoice_no']}
275
+ 📅 Date: {sale['sale_date']}
276
+ 💰 Total Amount: ₹{sale['total_amount']:,.2f}
277
+
278
+ 📦 Items Purchased:
279
+ """
280
+
281
+ # Add items to message
282
+ for _, item in items_details.iterrows():
283
+ message += f"• {item['product_name']}: {item['quantity']} x ₹{item['rate']} = ₹{item['amount']}\n"
284
+
285
+ message += f"""
286
+
287
+ Payment Status: {sale['payment_status']}
288
+
289
+ We appreciate your business! 🙏
290
+
291
+ For any queries, contact us.
292
+
293
+ Thank you!"""
294
+
295
+ # Send message
296
+ success = whatsapp_manager.send_message(customer['mobile'], message)
297
+ if success:
298
+ st.success("📱 WhatsApp notification sent to customer!")
299
+ else:
300
+ st.warning("⚠️ Failed to send WhatsApp notification")
301
+
302
+ return success
303
+
304
+ except Exception as e:
305
+ st.warning(f"Could not send notification: {e}")
306
+ return False
307
+
308
+ def show_quick_sale_summary(db, sale_id, sale_items, customer_id):
309
+ """Show comprehensive sale summary"""
310
+ st.markdown("## 🎉 Sale Completed Successfully!")
311
+
312
+ try:
313
+ # Get sale and customer details
314
+ sale_details = db.get_dataframe('sales', f"SELECT * FROM sales WHERE sale_id = {sale_id}")
315
+ customer_details = db.get_dataframe('customers', f"SELECT * FROM customers WHERE customer_id = {customer_id}")
316
+
317
+ if sale_details.empty or customer_details.empty:
318
+ return
319
+
320
+ sale = sale_details.iloc[0]
321
+ customer = customer_details.iloc[0]
322
+
323
+ # Display in columns
324
+ col1, col2 = st.columns(2)
325
+
326
+ with col1:
327
+ st.subheader("Customer Details")
328
+ st.write(f"**Name:** {customer['name']}")
329
+ if customer['mobile']:
330
+ st.write(f"**Mobile:** {customer['mobile']}")
331
+ if customer['village']:
332
+ st.write(f"**Village:** {customer['village']}")
333
+
334
+ st.subheader("Sale Details")
335
+ st.write(f"**Invoice No:** {sale['invoice_no']}")
336
+ st.write(f"**Sale Date:** {sale['sale_date']}")
337
+ st.write(f"**Payment Status:** {sale['payment_status']}")
338
+ if sale['notes']:
339
+ st.write(f"**Notes:** {sale['notes']}")
340
+
341
+ with col2:
342
+ st.subheader("Financial Summary")
343
+ st.metric("Total Amount", f"₹{sale['total_amount']:,.2f}")
344
+
345
+ # Get payment details
346
+ payment_details = db.get_dataframe('payments', f"SELECT * FROM payments WHERE sale_id = {sale_id}")
347
+ if not payment_details.empty:
348
+ total_paid = payment_details['amount'].sum()
349
+ pending = sale['total_amount'] - total_paid
350
+ st.metric("Amount Paid", f"₹{total_paid:,.2f}")
351
+ st.metric("Pending Amount", f"₹{pending:,.2f}")
352
+
353
+ st.subheader("Items Summary")
354
+ for item in sale_items:
355
+ st.write(f"• {item['product_name']}: {item['quantity']} x ₹{item['rate']}")
356
+
357
+ # Quick actions
358
+ st.markdown("### ⚡ Quick Actions")
359
+ col1, col2, col3 = st.columns(3)
360
+
361
+ with col1:
362
+ if st.button("📋 View All Sales"):
363
+ # This would switch to sales history tab in a real implementation
364
+ st.info("Navigate to Sales History tab")
365
+
366
+ with col2:
367
+ if st.button("👥 View Customer"):
368
+ st.info(f"Customer: {customer['name']}")
369
+
370
+ with col3:
371
+ if st.button("💳 Record Payment"):
372
+ st.info("Use Payments page to record additional payments")
373
+
374
+ except Exception as e:
375
+ st.error(f"Error displaying sale summary: {e}")
376
+
377
+ def show_sales_history_tab(db):
378
+ """Show tab for sales history and management"""
379
+ st.subheader("Sales History & Management")
380
+
381
+ try:
382
+ # Quick filters
383
+ col1, col2, col3, col4 = st.columns(4)
384
+
385
+ with col1:
386
+ date_filter = st.selectbox("Date Filter",
387
+ ["All", "Today", "Last 7 days", "This month", "Custom"])
388
+
389
+ with col2:
390
+ status_filter = st.multiselect("Payment Status",
391
+ ["Pending", "Partial", "Paid"],
392
+ default=["Pending", "Partial", "Paid"])
393
+
394
+ with col3:
395
+ search_term = st.text_input("Search Invoice/Customer")
396
+
397
+ with col4:
398
+ show_rows = st.selectbox("Show", [10, 25, 50, 100], index=0)
399
+
400
+ # Build query
401
+ query = '''
402
+ SELECT s.*, c.name as customer_name, c.village, c.mobile,
403
+ COALESCE(SUM(p.amount), 0) as paid_amount,
404
+ (s.total_amount - COALESCE(SUM(p.amount), 0)) as pending_amount
405
+ FROM sales s
406
+ LEFT JOIN customers c ON s.customer_id = c.customer_id
407
+ LEFT JOIN payments p ON s.sale_id = p.sale_id
408
+ '''
409
+
410
+ conditions = []
411
+ if status_filter:
412
+ status_cond = " OR ".join([f"s.payment_status = '{status}'" for status in status_filter])
413
+ conditions.append(f"({status_cond})")
414
+
415
+ if search_term:
416
+ conditions.append(f"(s.invoice_no LIKE '%{search_term}%' OR c.name LIKE '%{search_term}%')")
417
+
418
+ if conditions:
419
+ query += " WHERE " + " AND ".join(conditions)
420
+
421
+ query += " GROUP BY s.sale_id ORDER BY s.sale_date DESC LIMIT " + str(show_rows)
422
+
423
+ # Get sales data
424
+ sales_data = db.get_dataframe('sales', query)
425
+
426
+ if not sales_data.empty:
427
+ st.write(f"**Showing {len(sales_data)} sales**")
428
+
429
+ # Enhanced display
430
+ display_df = sales_data[['invoice_no', 'sale_date', 'customer_name', 'village',
431
+ 'total_amount', 'paid_amount', 'pending_amount', 'payment_status']].copy()
432
+
433
+ # Formatting
434
+ display_df['total_amount'] = display_df['total_amount'].apply(lambda x: f"₹{x:,.2f}")
435
+ display_df['paid_amount'] = display_df['paid_amount'].apply(lambda x: f"₹{x:,.2f}")
436
+ display_df['pending_amount'] = display_df['pending_amount'].apply(lambda x: f"₹{x:,.2f}")
437
+
438
+ display_df.columns = ['Invoice', 'Date', 'Customer', 'Village', 'Total', 'Paid', 'Pending', 'Status']
439
+
440
+ st.dataframe(display_df, use_container_width=True)
441
+
442
+ # Summary metrics
443
+ total_sales = sales_data['total_amount'].sum()
444
+ total_pending = sales_data['pending_amount'].sum()
445
+
446
+ col1, col2, col3 = st.columns(3)
447
+ with col1:
448
+ st.metric("Total Sales Value", f"₹{total_sales:,.2f}")
449
+ with col2:
450
+ st.metric("Total Pending", f"₹{total_pending:,.2f}")
451
+ with col3:
452
+ if total_sales > 0:
453
+ collection_rate = ((total_sales - total_pending) / total_sales) * 100
454
+ st.metric("Collection Rate", f"{collection_rate:.1f}%")
455
+
456
+ else:
457
+ st.info("No sales found matching the current filters.")
458
+
459
+ except Exception as e:
460
+ st.error(f"Error loading sales history: {e}")
461
+
462
+ def show_sales_analytics_tab(db):
463
+ """Show tab for sales analytics"""
464
+ st.subheader("Sales Analytics & Insights")
465
+
466
+ try:
467
+ # Date range
468
+ col1, col2 = st.columns(2)
469
+ with col1:
470
+ start_date = st.date_input("Start Date", datetime.now() - timedelta(days=30))
471
+ with col2:
472
+ end_date = st.date_input("End Date", datetime.now())
473
+
474
+ if start_date > end_date:
475
+ st.error("Start date cannot be after end date")
476
+ return
477
+
478
+ # Get analytics
479
+ analytics = db.get_sales_analytics(start_date.strftime('%Y-%m-%d'),
480
+ end_date.strftime('%Y-%m-%d'))
481
+
482
+ if analytics:
483
+ # Key metrics
484
+ st.subheader("📈 Performance Metrics")
485
+ cols = st.columns(4)
486
+ metrics = [
487
+ ("Total Sales", analytics.get('total_sales', 0), "💰"),
488
+ ("Total Revenue", f"₹{analytics.get('total_revenue', 0):,.2f}", "💵"),
489
+ ("Avg Sale", f"₹{analytics.get('avg_sale_value', 0):,.2f}", "📊"),
490
+ ("Unique Customers", analytics.get('unique_customers', 0), "👥")
491
+ ]
492
+
493
+ for col, (label, value, icon) in zip(cols, metrics):
494
+ with col:
495
+ st.metric(label, value)
496
+
497
+ # Additional analytics can be added here
498
+
499
+ except Exception as e:
500
+ st.error(f"Error loading analytics: {e}")
pages/system_dashboard.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # pages/system_dashboard.py
2
+ import streamlit as st
3
+ from utils.styling import create_metric_card, COLORS
4
+ import plotly.express as px
5
+ from datetime import datetime
6
+
7
+
8
+ def create_dashboard(db, analytics):
9
+ """Create the main dashboard with metrics and charts"""
10
+ st.markdown("<h1 class='main-header'>📊 Sales Dashboard</h1>", unsafe_allow_html=True)
11
+
12
+ # One-time notification after scheduling a demo
13
+ if st.session_state.get("demo_created_notification"):
14
+ st.success(f"✅ Demo #{st.session_state.demo_created_notification} added successfully!")
15
+ st.session_state.demo_created_notification = None
16
+
17
+ if not analytics or not db:
18
+ st.error("Analytics or Database not available.")
19
+ return
20
+
21
+ # Fetch analytics safely
22
+ try:
23
+ sales_summary = analytics.get_sales_summary()
24
+ demo_stats = analytics.get_demo_conversion_rates()
25
+ customer_analysis = analytics.get_customer_analysis()
26
+ payment_analysis = analytics.get_payment_analysis()
27
+ except Exception:
28
+ sales_summary = {"total_sales": 0, "pending_amount": 0}
29
+ demo_stats = {"conversion_rate": 0}
30
+ customer_analysis = {"total_customers": 0}
31
+ payment_analysis = {"total_pending": 0}
32
+
33
+ # ------------------- METRICS ROW -------------------
34
+ col1, col2, col3, col4 = st.columns(4)
35
+
36
+ with col1:
37
+ st.markdown(create_metric_card(f"₹{sales_summary['total_sales']:,.0f}", "Total Sales", "💰", COLORS["primary"]), unsafe_allow_html=True)
38
+
39
+ with col2:
40
+ st.markdown(create_metric_card(f"₹{sales_summary['pending_amount']:,.0f}", "Pending Payments", "⏳", COLORS["warning"]), unsafe_allow_html=True)
41
+
42
+ with col3:
43
+ st.markdown(create_metric_card(f"{demo_stats['conversion_rate']:.1f}%", "Demo Conversion", "🎯", COLORS["success"]), unsafe_allow_html=True)
44
+
45
+ with col4:
46
+ st.markdown(create_metric_card(f"{customer_analysis['total_customers']}", "Total Customers", "👥", COLORS["secondary"]), unsafe_allow_html=True)
47
+
48
+ # ------------------- SALES TREND + PAYMENT CHARTS -------------------
49
+ col1, col2 = st.columns(2)
50
+
51
+ with col1:
52
+ st.markdown("<h3 class='section-header'>Sales Trend</h3>", unsafe_allow_html=True)
53
+ try:
54
+ sales_trend = analytics.get_sales_trend()
55
+ if not sales_trend.empty:
56
+ fig = px.line(
57
+ sales_trend,
58
+ x="sale_date",
59
+ y="total_amount",
60
+ title="Daily Sales Trend",
61
+ color_discrete_sequence=[COLORS["primary"]],
62
+ )
63
+ st.plotly_chart(fig, use_container_width=True)
64
+ else:
65
+ st.info("No sales data available.")
66
+ except Exception:
67
+ st.info("Error loading sales trend.")
68
+
69
+ with col2:
70
+ st.markdown("<h3 class='section-header'>Payment Status</h3>", unsafe_allow_html=True)
71
+ try:
72
+ payment_data = analytics.get_payment_distribution()
73
+ if not payment_data.empty:
74
+ fig = px.pie(
75
+ payment_data,
76
+ values="amount",
77
+ names="payment_method",
78
+ title="Payment Methods Distribution",
79
+ )
80
+ st.plotly_chart(fig, use_container_width=True)
81
+ else:
82
+ st.info("No payment data available.")
83
+ except Exception:
84
+ st.info("Error loading payment distribution.")
85
+
86
+ # ------------------- RECENT SALES + UPCOMING DEMOS -------------------
87
+ col1, col2 = st.columns(2)
88
+
89
+ # Recent Sales
90
+ with col1:
91
+ st.markdown("<h3 class='section-header'>Recent Sales</h3>", unsafe_allow_html=True)
92
+ recent_sales = db.get_dataframe(
93
+ "sales",
94
+ """
95
+ SELECT s.invoice_no, c.name AS customer_name, c.village,
96
+ s.total_amount, s.sale_date
97
+ FROM sales s
98
+ JOIN customers c ON s.customer_id = c.customer_id
99
+ ORDER BY s.created_date DESC LIMIT 8
100
+ """,
101
+ )
102
+ if not recent_sales.empty:
103
+ st.dataframe(recent_sales, use_container_width=True, hide_index=True)
104
+ else:
105
+ st.info("No recent sales.")
106
+
107
+ # Upcoming Demos
108
+ with col2:
109
+ st.markdown("<h3 class='section-header'>Upcoming Demos</h3>", unsafe_allow_html=True)
110
+
111
+ upcoming_demos = db.get_dataframe(
112
+ "demos",
113
+ """
114
+ SELECT d.demo_id, c.name AS customer_name, c.village,
115
+ p.product_name, d.demo_date, d.demo_time
116
+ FROM demos d
117
+ LEFT JOIN customers c ON d.customer_id = c.customer_id
118
+ LEFT JOIN products p ON d.product_id = p.product_id
119
+ WHERE date(d.demo_date) >= date('now')
120
+ AND LOWER(TRIM(d.conversion_status)) = 'scheduled'
121
+ ORDER BY d.demo_date ASC, d.demo_time ASC
122
+ LIMIT 8
123
+ """,
124
+ )
125
+
126
+ if not upcoming_demos.empty:
127
+ upcoming_demos.columns = ["Demo ID", "Customer", "Village", "Product", "Date", "Time"]
128
+ st.dataframe(upcoming_demos, use_container_width=True, hide_index=True)
129
+
130
+ if st.button("📋 View All Demos"):
131
+ st.session_state.current_page = "demos"
132
+ st.rerun()
133
+ else:
134
+ st.warning("⚠️ No upcoming demos scheduled.")
135
+ if st.button("➕ Schedule a Demo"):
136
+ st.session_state.current_page = "demos"
137
+ st.rerun()
pages/whatsapp.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # pages/whatsapp.py
2
+ import streamlit as st
3
+ import pandas as pd
4
+
5
+ def show_whatsapp_page(db, whatsapp_manager):
6
+ """Show WhatsApp messaging page"""
7
+ st.title("💬 WhatsApp Messaging")
8
+
9
+ if not whatsapp_manager:
10
+ st.error("WhatsApp manager not available. Please install pywhatkit: pip install pywhatkit")
11
+ st.info("""
12
+ **To enable WhatsApp messaging:**
13
+ 1. Install: `pip install pywhatkit`
14
+ 2. Make sure you're logged into WhatsApp Web in your default browser
15
+ 3. Ensure phone numbers include country code (e.g., +91 for India)
16
+ """)
17
+ else:
18
+ tab1, tab2, tab3, tab4 = st.tabs(["Single Message", "Bulk Messages", "Templates", "Message History"])
19
+
20
+ with tab1:
21
+ show_single_message_tab(db, whatsapp_manager)
22
+
23
+ with tab2:
24
+ show_bulk_messages_tab(db, whatsapp_manager)
25
+
26
+ with tab3:
27
+ show_templates_tab()
28
+
29
+ with tab4:
30
+ show_message_history_tab(db)
31
+
32
+ def show_single_message_tab(db, whatsapp_manager):
33
+ """Show single message tab"""
34
+ st.subheader("📱 Send Single Message")
35
+
36
+ col1, col2 = st.columns(2)
37
+
38
+ with col1:
39
+ # Option 1: Select from existing customers
40
+ st.write("**Select from Existing Customers**")
41
+ customers = db.get_dataframe('customers')
42
+ if not customers.empty:
43
+ customer_options = {f"{row['name']} ({row['mobile']}) - {row['village']}": row for _, row in customers.iterrows()}
44
+ selected_customer_key = st.selectbox("Choose Customer", options=[""] + list(customer_options.keys()))
45
+
46
+ if selected_customer_key:
47
+ customer_data = customer_options[selected_customer_key]
48
+ st.write(f"**Selected:** {customer_data['name']}")
49
+ st.write(f"**Mobile:** {customer_data['mobile']}")
50
+ st.write(f"**Village:** {customer_data['village']}")
51
+
52
+ # Pre-fill message with template
53
+ message_template = st.selectbox("Quick Template", [
54
+ "Custom Message",
55
+ "Payment Reminder",
56
+ "Demo Follow-up",
57
+ "New Product Announcement",
58
+ "Festival Greeting"
59
+ ])
60
+
61
+ with col2:
62
+ # Option 2: Manual entry
63
+ st.write("**Or Enter Manually**")
64
+ manual_name = st.text_input("Recipient Name")
65
+ manual_mobile = st.text_input("Mobile Number (with country code)", placeholder="+91XXXXXXXXXX")
66
+
67
+ # Message content and sending logic would continue here...
68
+ # (I'll show the rest in the next message due to length)
utils/__init__.py ADDED
File without changes
utils/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (138 Bytes). View file
 
utils/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (142 Bytes). View file
 
utils/__pycache__/helpers.cpython-310.pyc ADDED
Binary file (1.63 kB). View file
 
utils/__pycache__/helpers.cpython-313.pyc ADDED
Binary file (2.58 kB). View file
 
utils/__pycache__/styling.cpython-310.pyc ADDED
Binary file (2.32 kB). View file
 
utils/__pycache__/styling.cpython-313.pyc ADDED
Binary file (2.34 kB). View file