entropy25 commited on
Commit
4f9b91b
Β·
verified Β·
1 Parent(s): 67a4318

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +629 -146
app.py CHANGED
@@ -29,11 +29,14 @@ COLORS = {
29
  'warning': '#f59e0b',
30
  'danger': '#ef4444',
31
  'purple': '#8b5cf6',
32
- 'indigo': '#6366f1',
33
  'blue': '#3b82f6',
34
- 'gray': '#6b7280'
35
  }
36
 
 
 
 
37
  class B2BCustomerAnalytics:
38
  def __init__(self):
39
  self.df = None
@@ -49,26 +52,21 @@ class B2BCustomerAnalytics:
49
 
50
  self.df = pd.read_csv(file.name)
51
 
52
- # Basic validation
53
  required_columns = ['customer_id', 'order_date', 'amount']
54
  missing_cols = [col for col in required_columns if col not in self.df.columns]
55
  if missing_cols:
56
  return f"Missing required columns: {missing_cols}", None, None, None
57
 
58
- # Convert order_date to datetime
59
  self.df['order_date'] = pd.to_datetime(self.df['order_date'])
60
 
61
- # Calculate RFM metrics if not present
62
- if 'recency_days' not in self.df.columns:
63
  self.df = self.calculate_rfm_metrics(self.df)
64
 
65
- # Customer segmentation
66
  self.df = self.perform_customer_segmentation(self.df)
67
 
68
- # Generate modern dashboard
69
- dashboard_html, metrics_cards = self.generate_modern_dashboard()
70
 
71
- return "Data loaded successfully", dashboard_html, self.df.head(20), metrics_cards
72
 
73
  except Exception as e:
74
  return f"Error loading data: {str(e)}", None, None, None
@@ -98,7 +96,6 @@ class B2BCustomerAnalytics:
98
  'monetary': 'first'
99
  }).reset_index()
100
 
101
- # Create RFM scores (1-5 scale)
102
  customer_df['R_Score'] = pd.qcut(customer_df['recency_days'].rank(method='first'), 5, labels=[5,4,3,2,1])
103
  customer_df['F_Score'] = pd.qcut(customer_df['frequency'].rank(method='first'), 5, labels=[1,2,3,4,5])
104
  customer_df['M_Score'] = pd.qcut(customer_df['monetary'].rank(method='first'), 5, labels=[1,2,3,4,5])
@@ -126,6 +123,7 @@ class B2BCustomerAnalytics:
126
  return 'Others'
127
 
128
  customer_df['Segment'] = customer_df.apply(segment_customers, axis=1)
 
129
  customer_df['Churn_Risk'] = customer_df.apply(lambda x:
130
  'High' if x['Segment'] in ['Lost Customers', 'At Risk'] else
131
  'Medium' if x['Segment'] in ['Others', 'Cannot Lose Them'] else 'Low', axis=1)
@@ -135,29 +133,23 @@ class B2BCustomerAnalytics:
135
 
136
  return df_with_segments
137
 
138
- def generate_modern_dashboard(self):
139
- """Generate modern dashboard with clean design"""
140
  if self.df is None:
141
  return "No data loaded", ""
142
 
143
- # Calculate KPIs
144
  total_customers = self.df['customer_id'].nunique()
145
  total_orders = len(self.df)
146
  total_revenue = self.df['amount'].sum()
147
  avg_order_value = self.df['amount'].mean()
148
 
149
- # Risk and segment distributions
150
  segment_dist = self.df.groupby('customer_id')['Segment'].first().value_counts()
151
  risk_dist = self.df.groupby('customer_id')['Churn_Risk'].first().value_counts()
152
 
153
- high_risk_customers = risk_dist.get('High', 0)
154
- champion_customers = segment_dist.get('Champions', 0)
155
- healthy_customers = risk_dist.get('Low', 0)
156
-
157
- # Modern dashboard HTML
158
- dashboard_html = f"""
159
- <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 3rem; border-radius: 1rem; color: white; margin-bottom: 3rem; text-align: center;">
160
- <h1 style="font-size: 2.5rem; font-weight: bold; margin-bottom: 0.5rem; font-family: 'Inter', sans-serif;">
161
  B2B Customer Analytics Platform
162
  </h1>
163
  <p style="font-size: 1.2rem; opacity: 0.9;">
@@ -165,89 +157,76 @@ class B2BCustomerAnalytics:
165
  </p>
166
  </div>
167
 
168
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1.5rem; margin-bottom: 3rem;">
169
-
170
- <div style="background: white; padding: 2rem; border-radius: 1rem; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); border-left: 4px solid #3b82f6; transition: transform 0.2s;">
171
- <div style="display: flex; items-center: justify-between; margin-bottom: 1rem;">
172
- <div style="padding: 0.75rem; background: #eff6ff; border-radius: 0.5rem;">
173
- <div style="width: 1.5rem; height: 1.5rem; background: #3b82f6; border-radius: 50%;"></div>
174
- </div>
175
  <span style="font-size: 2rem; font-weight: bold; color: #3b82f6;">{total_customers:,}</span>
176
  </div>
177
- <h3 style="color: #1f2937; font-weight: 600; margin-bottom: 0.25rem;">Total Customers</h3>
178
- <p style="color: #6b7280; font-size: 0.875rem;">Active enterprise clients</p>
179
  </div>
180
 
181
- <div style="background: white; padding: 2rem; border-radius: 1rem; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); border-left: 4px solid #10b981; transition: transform 0.2s;">
182
- <div style="display: flex; items-center: justify-between; margin-bottom: 1rem;">
183
- <div style="padding: 0.75rem; background: #f0fdf4; border-radius: 0.5rem;">
184
- <div style="width: 1.5rem; height: 1.5rem; background: #10b981; border-radius: 50%;"></div>
185
- </div>
186
  <span style="font-size: 2rem; font-weight: bold; color: #10b981;">${(total_revenue/1000000):.1f}M</span>
187
  </div>
188
- <h3 style="color: #1f2937; font-weight: 600; margin-bottom: 0.25rem;">Total Revenue</h3>
189
- <p style="color: #6b7280; font-size: 0.875rem;">Contract value sum</p>
190
  </div>
191
 
192
- <div style="background: white; padding: 2rem; border-radius: 1rem; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); border-left: 4px solid #8b5cf6; transition: transform 0.2s;">
193
- <div style="display: flex; items-center: justify-between; margin-bottom: 1rem;">
194
- <div style="padding: 0.75rem; background: #faf5ff; border-radius: 0.5rem;">
195
- <div style="width: 1.5rem; height: 1.5rem; background: #8b5cf6; border-radius: 50%;"></div>
196
- </div>
197
- <span style="font-size: 2rem; font-weight: bold; color: #8b5cf6;">${(avg_order_value/1000):.0f}K</span>
198
  </div>
199
- <h3 style="color: #1f2937; font-weight: 600; margin-bottom: 0.25rem;">Avg Order Value</h3>
200
- <p style="color: #6b7280; font-size: 0.875rem;">Per customer average</p>
201
  </div>
202
 
203
- <div style="background: white; padding: 2rem; border-radius: 1rem; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); border-left: 4px solid #ef4444; transition: transform 0.2s;">
204
- <div style="display: flex; items-center: justify-between; margin-bottom: 1rem;">
205
- <div style="padding: 0.75rem; background: #fef2f2; border-radius: 0.5rem;">
206
- <div style="width: 1.5rem; height: 1.5rem; background: #ef4444; border-radius: 50%;"></div>
207
- </div>
208
- <span style="font-size: 2rem; font-weight: bold; color: #ef4444;">{high_risk_customers}</span>
209
  </div>
210
- <h3 style="color: #1f2937; font-weight: 600; margin-bottom: 0.25rem;">High Risk Clients</h3>
211
- <p style="color: #6b7280; font-size: 0.875rem;">Require immediate attention</p>
212
  </div>
213
 
214
- <div style="background: white; padding: 2rem; border-radius: 1rem; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); border-left: 4px solid #f59e0b; transition: transform 0.2s;">
215
- <div style="display: flex; items-center: justify-between; margin-bottom: 1rem;">
216
- <div style="padding: 0.75rem; background: #fffbeb; border-radius: 0.5rem;">
217
- <div style="width: 1.5rem; height: 1.5rem; background: #f59e0b; border-radius: 50%;"></div>
218
- </div>
219
- <span style="font-size: 2rem; font-weight: bold; color: #f59e0b;">{champion_customers}</span>
220
  </div>
221
- <h3 style="color: #1f2937; font-weight: 600; margin-bottom: 0.25rem;">Champion Clients</h3>
222
- <p style="color: #6b7280; font-size: 0.875rem;">Top tier customers</p>
223
  </div>
224
 
225
- <div style="background: white; padding: 2rem; border-radius: 1rem; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); border-left: 4px solid #06b6d4; transition: transform 0.2s;">
226
- <div style="display: flex; items-center: justify-between; margin-bottom: 1rem;">
227
- <div style="padding: 0.75rem; background: #f0fdfa; border-radius: 0.5rem;">
228
- <div style="width: 1.5rem; height: 1.5rem; background: #06b6d4; border-radius: 50%;"></div>
229
- </div>
230
- <span style="font-size: 2rem; font-weight: bold; color: #06b6d4;">{healthy_customers}</span>
231
  </div>
232
- <h3 style="color: #1f2937; font-weight: 600; margin-bottom: 0.25rem;">Healthy Clients</h3>
233
- <p style="color: #6b7280; font-size: 0.875rem;">Low churn risk</p>
234
  </div>
235
  </div>
236
  """
237
 
238
- metrics_cards = [
239
- ["Total Customers", f"{total_customers:,}", "#3b82f6"],
240
- ["Total Revenue", f"${total_revenue/1000000:.1f}M", "#10b981"],
241
- ["Avg Order Value", f"${avg_order_value/1000:.0f}K", "#8b5cf6"],
242
- ["High Risk Customers", f"{high_risk_customers}", "#ef4444"],
243
- ["Champion Customers", f"{champion_customers}", "#f59e0b"],
244
- ["Healthy Customers", f"{healthy_customers}", "#06b6d4"]
245
  ]
246
 
247
- return dashboard_html, metrics_cards
248
 
249
  def train_churn_model(self):
250
- """Train churn prediction model with modern UI feedback"""
251
  if self.df is None:
252
  return "No data available. Please upload a CSV file first.", None
253
 
@@ -265,10 +244,15 @@ class B2BCustomerAnalytics:
265
  'first_order', 'last_order']
266
 
267
  customer_features['std_amount'].fillna(0, inplace=True)
 
268
  customer_features['customer_lifetime'] = (customer_features['last_order'] - customer_features['first_order']).dt.days
269
  customer_features['customer_lifetime'].fillna(0, inplace=True)
270
 
271
- customer_features['churn_label'] = (customer_features['recency_days'] > 90).astype(int)
 
 
 
 
272
 
273
  feature_cols = ['recency_days', 'frequency', 'monetary', 'avg_amount', 'std_amount',
274
  'min_amount', 'max_amount', 'customer_lifetime']
@@ -282,7 +266,7 @@ class B2BCustomerAnalytics:
282
  self.model.fit(X_train, y_train)
283
 
284
  y_pred = self.model.predict(X_test)
285
- accuracy = accuracy_score(y_test, y_pred)
286
 
287
  self.feature_importance = pd.DataFrame({
288
  'feature': feature_cols,
@@ -293,12 +277,13 @@ class B2BCustomerAnalytics:
293
  customer_features['churn_probability'] = all_predictions
294
  self.predictions = customer_features
295
 
296
- # Modern results display
 
297
  results_html = f"""
298
- <div style="background: white; padding: 2.5rem; border-radius: 1rem; box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1); border: 1px solid #e5e7eb; margin-top: 2rem;">
299
  <div style="text-align: center; margin-bottom: 2rem;">
300
- <div style="display: inline-block; padding: 1rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 50%; margin-bottom: 1rem;">
301
- <div style="width: 2rem; height: 2rem; background: white; border-radius: 50%; opacity: 0.3;"></div>
302
  </div>
303
  <h3 style="font-size: 1.75rem; font-weight: bold; color: #1f2937; margin-bottom: 0.5rem;">
304
  Model Training Completed
@@ -308,36 +293,31 @@ class B2BCustomerAnalytics:
308
 
309
  <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1.5rem; margin-bottom: 2rem;">
310
  <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 1.5rem; border-radius: 1rem; text-align: center; color: white;">
311
- <div style="font-size: 2rem; font-weight: bold; margin-bottom: 0.5rem;">{accuracy:.1%}</div>
312
  <div style="font-size: 1rem; opacity: 0.9;">Model Accuracy</div>
313
  </div>
314
  <div style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); padding: 1.5rem; border-radius: 1rem; text-align: center; color: white;">
315
- <div style="font-size: 2rem; font-weight: bold; margin-bottom: 0.5rem;">{len(feature_cols)}</div>
316
  <div style="font-size: 1rem; opacity: 0.9;">Features Used</div>
317
  </div>
318
  <div style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); padding: 1.5rem; border-radius: 1rem; text-align: center; color: white;">
319
- <div style="font-size: 2rem; font-weight: bold; margin-bottom: 0.5rem;">{len(X_train)}</div>
320
  <div style="font-size: 1rem; opacity: 0.9;">Training Samples</div>
321
  </div>
322
  <div style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); padding: 1.5rem; border-radius: 1rem; text-align: center; color: white;">
323
- <div style="font-size: 2rem; font-weight: bold; margin-bottom: 0.5rem;">{len(X_test)}</div>
324
  <div style="font-size: 1rem; opacity: 0.9;">Test Samples</div>
325
  </div>
326
  </div>
327
 
328
- <div style="background: #f8fafc; padding: 2rem; border-radius: 1rem; border: 1px solid #e2e8f0;">
329
- <h4 style="font-weight: 600; color: #374151; margin-bottom: 1.5rem; font-size: 1.25rem;">Top Feature Importance</h4>
330
- <div style="space-y: 1rem;">
331
  {''.join([f'''<div style="display: flex; justify-content: space-between; align-items: center; padding: 1rem 0; border-bottom: 1px solid #e5e7eb;">
332
- <span style="font-weight: 500; color: #374151; font-size: 1rem;">{row['feature'].replace('_', ' ').title()}</span>
333
- <div style="display: flex; align-items: center;">
334
- <div style="width: 100px; height: 8px; background: #e5e7eb; border-radius: 4px; margin-right: 1rem;">
335
- <div style="height: 100%; background: #3b82f6; border-radius: 4px; width: {row['importance']*100:.1f}%;"></div>
336
- </div>
337
- <span style="background: #3b82f6; color: white; padding: 0.25rem 0.75rem; border-radius: 9999px; font-size: 0.875rem; font-weight: 500;">
338
- {row['importance']:.3f}
339
- </span>
340
- </div>
341
  </div>''' for _, row in self.feature_importance.head(5).iterrows()])}
342
  </div>
343
  </div>
@@ -350,7 +330,7 @@ class B2BCustomerAnalytics:
350
  return f"Error training model: {str(e)}", None
351
 
352
  def create_model_performance_chart(self):
353
- """Create clean model performance visualization"""
354
  if self.feature_importance is None:
355
  return None
356
 
@@ -362,29 +342,31 @@ class B2BCustomerAnalytics:
362
  title='Feature Importance Analysis',
363
  labels={'importance': 'Importance Score', 'feature': 'Features'},
364
  color='importance',
365
- color_continuous_scale=['#e0e7ff', '#6366f1']
366
  )
367
 
368
  fig.update_layout(
369
- height=400,
370
  showlegend=False,
371
  plot_bgcolor='white',
372
  paper_bgcolor='white',
373
  title={
374
- 'text': 'Feature Importance Analysis',
375
  'x': 0.5,
376
  'xanchor': 'center',
377
- 'font': {'size': 18, 'color': '#1f2937', 'family': 'Inter, sans-serif'}
378
  },
379
- font=dict(family="Inter, sans-serif", color='#374151'),
380
  yaxis={'categoryorder': 'total ascending'},
381
- margin=dict(l=20, r=20, t=60, b=20)
 
 
382
  )
383
 
384
  return fig
385
 
386
  def create_visualizations(self):
387
- """Create modern, clean visualizations"""
388
  if self.df is None:
389
  return None, None, None, None
390
 
@@ -396,21 +378,16 @@ class B2BCustomerAnalytics:
396
  segment_data,
397
  values='Count',
398
  names='Segment',
399
- title='Customer Segment Distribution',
400
  hole=0.4,
401
  color_discrete_sequence=['#6366f1', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899']
402
  )
403
- fig1.update_traces(
404
- textposition='inside',
405
- textinfo='percent+label',
406
- textfont_size=12,
407
- textfont_family='Inter'
408
- )
409
  fig1.update_layout(
410
- height=400,
411
  showlegend=True,
412
- title={'x': 0.5, 'xanchor': 'center', 'font': {'size': 18, 'color': '#1f2937', 'family': 'Inter'}},
413
- font=dict(family="Inter, sans-serif", color='#374151'),
414
  paper_bgcolor='white',
415
  plot_bgcolor='white'
416
  )
@@ -428,19 +405,20 @@ class B2BCustomerAnalytics:
428
  x='recency_days',
429
  y='frequency',
430
  size='monetary',
431
- color='Segment',
432
- title='RFM Analysis - Customer Behavior Matrix',
433
  labels={
434
- 'recency_days': 'Recency (Days)',
435
- 'frequency': 'Frequency (Orders)',
436
- 'monetary': 'Monetary (Revenue)'
437
  },
438
- color_discrete_sequence=['#6366f1', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6']
 
439
  )
440
  fig2.update_layout(
441
- height=400,
442
- title={'x': 0.5, 'xanchor': 'center', 'font': {'size': 18, 'color': '#1f2937', 'family': 'Inter'}},
443
- font=dict(family="Inter, sans-serif", color='#374151'),
444
  paper_bgcolor='white',
445
  plot_bgcolor='white'
446
  )
@@ -451,11 +429,12 @@ class B2BCustomerAnalytics:
451
  self.predictions,
452
  x='churn_probability',
453
  nbins=20,
454
- title='Churn Probability Distribution',
455
  labels={'churn_probability': 'Churn Probability', 'count': 'Number of Customers'},
456
- color_discrete_sequence=['#6366f1']
457
  )
458
- fig3.add_vline(x=0.5, line_dash="dash", line_color="#ef4444", annotation_text="High Risk Threshold")
 
459
  else:
460
  risk_data = self.df.groupby('customer_id')['Churn_Risk'].first().value_counts().reset_index()
461
  risk_data.columns = ['Risk_Level', 'Count']
@@ -464,18 +443,18 @@ class B2BCustomerAnalytics:
464
  risk_data,
465
  x='Risk_Level',
466
  y='Count',
467
- title='Customer Churn Risk Distribution',
468
  color='Risk_Level',
469
  color_discrete_map=colors_map
470
  )
471
 
472
  fig3.update_layout(
473
- height=400,
474
  showlegend=False,
475
- title={'x': 0.5, 'xanchor': 'center', 'font': {'size': 18, 'color': '#1f2937', 'family': 'Inter'}},
476
- font=dict(family="Inter, sans-serif", color='#374151'),
477
- paper_bgcolor='white',
478
- plot_bgcolor='white'
479
  )
480
 
481
  # 4. Revenue Trends
@@ -487,18 +466,522 @@ class B2BCustomerAnalytics:
487
  monthly_revenue,
488
  x='order_month',
489
  y='amount',
490
- title='Monthly Revenue Trends',
491
  labels={'amount': 'Revenue ($)', 'order_month': 'Month'},
492
  line_shape='spline'
493
  )
494
- fig4.update_traces(line_color='#6366f1', line_width=3)
495
  fig4.update_layout(
496
- height=400,
497
- title={'x': 0.5, 'xanchor': 'center', 'font': {'size': 18, 'color': '#1f2937', 'family': 'Inter'}},
498
- font=dict(family="Inter, sans-serif", color='#374151'),
499
- paper_bgcolor='white',
500
  plot_bgcolor='white',
501
- xaxis_tickangle=-45
 
 
 
502
  )
503
 
504
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  'warning': '#f59e0b',
30
  'danger': '#ef4444',
31
  'purple': '#8b5cf6',
32
+ 'pink': '#ec4899',
33
  'blue': '#3b82f6',
34
+ 'indigo': '#6366f1'
35
  }
36
 
37
+ plt.style.use('seaborn-v0_8-whitegrid')
38
+ sns.set_palette("husl")
39
+
40
  class B2BCustomerAnalytics:
41
  def __init__(self):
42
  self.df = None
 
52
 
53
  self.df = pd.read_csv(file.name)
54
 
 
55
  required_columns = ['customer_id', 'order_date', 'amount']
56
  missing_cols = [col for col in required_columns if col not in self.df.columns]
57
  if missing_cols:
58
  return f"Missing required columns: {missing_cols}", None, None, None
59
 
 
60
  self.df['order_date'] = pd.to_datetime(self.df['order_date'])
61
 
62
+ if 'recency_days' not in self.df.columns or 'frequency' not in self.df.columns or 'monetary' not in self.df.columns:
 
63
  self.df = self.calculate_rfm_metrics(self.df)
64
 
 
65
  self.df = self.perform_customer_segmentation(self.df)
66
 
67
+ summary_html, kpi_cards = self.generate_summary_dashboard()
 
68
 
69
+ return "Data loaded successfully!", summary_html, self.df.head(20), kpi_cards
70
 
71
  except Exception as e:
72
  return f"Error loading data: {str(e)}", None, None, None
 
96
  'monetary': 'first'
97
  }).reset_index()
98
 
 
99
  customer_df['R_Score'] = pd.qcut(customer_df['recency_days'].rank(method='first'), 5, labels=[5,4,3,2,1])
100
  customer_df['F_Score'] = pd.qcut(customer_df['frequency'].rank(method='first'), 5, labels=[1,2,3,4,5])
101
  customer_df['M_Score'] = pd.qcut(customer_df['monetary'].rank(method='first'), 5, labels=[1,2,3,4,5])
 
123
  return 'Others'
124
 
125
  customer_df['Segment'] = customer_df.apply(segment_customers, axis=1)
126
+
127
  customer_df['Churn_Risk'] = customer_df.apply(lambda x:
128
  'High' if x['Segment'] in ['Lost Customers', 'At Risk'] else
129
  'Medium' if x['Segment'] in ['Others', 'Cannot Lose Them'] else 'Low', axis=1)
 
133
 
134
  return df_with_segments
135
 
136
+ def generate_summary_dashboard(self):
137
+ """Generate modern dashboard summary with KPI cards"""
138
  if self.df is None:
139
  return "No data loaded", ""
140
 
 
141
  total_customers = self.df['customer_id'].nunique()
142
  total_orders = len(self.df)
143
  total_revenue = self.df['amount'].sum()
144
  avg_order_value = self.df['amount'].mean()
145
 
 
146
  segment_dist = self.df.groupby('customer_id')['Segment'].first().value_counts()
147
  risk_dist = self.df.groupby('customer_id')['Churn_Risk'].first().value_counts()
148
 
149
+ # Create modern horizontal dashboard
150
+ summary_html = f"""
151
+ <div style="background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); padding: 2rem; border-radius: 1rem; color: white; margin-bottom: 2rem; text-align: center;">
152
+ <h1 style="font-size: 2.5rem; font-weight: bold; margin-bottom: 0.5rem;">
 
 
 
 
153
  B2B Customer Analytics Platform
154
  </h1>
155
  <p style="font-size: 1.2rem; opacity: 0.9;">
 
157
  </p>
158
  </div>
159
 
160
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1.5rem; margin-bottom: 3rem;">
161
+ <div style="background: white; padding: 1.5rem; border-radius: 1rem; box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1); border-left: 4px solid #3b82f6;">
162
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
163
+ <div style="padding: 0.75rem; background: #dbeafe; border-radius: 0.5rem; color: #1d4ed8;">πŸ“Š</div>
 
 
 
164
  <span style="font-size: 2rem; font-weight: bold; color: #3b82f6;">{total_customers:,}</span>
165
  </div>
166
+ <h3 style="color: #1f2937; font-weight: 600; margin: 0;">Total Customers</h3>
167
+ <p style="color: #6b7280; font-size: 0.875rem; margin: 0.25rem 0 0 0;">Active enterprise clients</p>
168
  </div>
169
 
170
+ <div style="background: white; padding: 1.5rem; border-radius: 1rem; box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1); border-left: 4px solid #10b981;">
171
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
172
+ <div style="padding: 0.75rem; background: #d1fae5; border-radius: 0.5rem; color: #047857;">πŸ’°</div>
 
 
173
  <span style="font-size: 2rem; font-weight: bold; color: #10b981;">${(total_revenue/1000000):.1f}M</span>
174
  </div>
175
+ <h3 style="color: #1f2937; font-weight: 600; margin: 0;">Total Revenue</h3>
176
+ <p style="color: #6b7280; font-size: 0.875rem; margin: 0.25rem 0 0 0;">Contract value sum</p>
177
  </div>
178
 
179
+ <div style="background: white; padding: 1.5rem; border-radius: 1rem; box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1); border-left: 4px solid #8b5cf6;">
180
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
181
+ <div style="padding: 0.75rem; background: #ede9fe; border-radius: 0.5rem; color: #7c3aed;">πŸ“ˆ</div>
182
+ <span style="font-size: 2rem; font-weight: bold; color: #8b5cf6;">${avg_order_value:.0f}</span>
 
 
183
  </div>
184
+ <h3 style="color: #1f2937; font-weight: 600; margin: 0;">Avg Order Value</h3>
185
+ <p style="color: #6b7280; font-size: 0.875rem; margin: 0.25rem 0 0 0;">Per order average</p>
186
  </div>
187
 
188
+ <div style="background: white; padding: 1.5rem; border-radius: 1rem; box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1); border-left: 4px solid #ef4444;">
189
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
190
+ <div style="padding: 0.75rem; background: #fee2e2; border-radius: 0.5rem; color: #dc2626;">🚨</div>
191
+ <span style="font-size: 2rem; font-weight: bold; color: #ef4444;">{risk_dist.get('High', 0)}</span>
 
 
192
  </div>
193
+ <h3 style="color: #1f2937; font-weight: 600; margin: 0;">High Risk Clients</h3>
194
+ <p style="color: #6b7280; font-size: 0.875rem; margin: 0.25rem 0 0 0;">Need immediate attention</p>
195
  </div>
196
 
197
+ <div style="background: white; padding: 1.5rem; border-radius: 1rem; box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1); border-left: 4px solid #f59e0b;">
198
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
199
+ <div style="padding: 0.75rem; background: #fef3c7; border-radius: 0.5rem; color: #d97706;">πŸ†</div>
200
+ <span style="font-size: 2rem; font-weight: bold; color: #f59e0b;">{segment_dist.get('Champions', 0)}</span>
 
 
201
  </div>
202
+ <h3 style="color: #1f2937; font-weight: 600; margin: 0;">Champion Customers</h3>
203
+ <p style="color: #6b7280; font-size: 0.875rem; margin: 0.25rem 0 0 0;">Top tier clients</p>
204
  </div>
205
 
206
+ <div style="background: white; padding: 1.5rem; border-radius: 1rem; box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1); border-left: 4px solid #06b6d4;">
207
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
208
+ <div style="padding: 0.75rem; background: #cffafe; border-radius: 0.5rem; color: #0891b2;">βœ…</div>
209
+ <span style="font-size: 2rem; font-weight: bold; color: #06b6d4;">{risk_dist.get('Low', 0)}</span>
 
 
210
  </div>
211
+ <h3 style="color: #1f2937; font-weight: 600; margin: 0;">Healthy Customers</h3>
212
+ <p style="color: #6b7280; font-size: 0.875rem; margin: 0.25rem 0 0 0;">Low churn risk</p>
213
  </div>
214
  </div>
215
  """
216
 
217
+ kpi_data = [
218
+ ["Total Customers", f"{total_customers:,}", "πŸ‘₯", "#3b82f6"],
219
+ ["Total Revenue", f"${total_revenue/1000000:.1f}M", "πŸ’°", "#10b981"],
220
+ ["Avg Order Value", f"${avg_order_value:.0f}", "πŸ“ˆ", "#8b5cf6"],
221
+ ["High Risk Customers", f"{risk_dist.get('High', 0)}", "🚨", "#ef4444"],
222
+ ["Champion Customers", f"{segment_dist.get('Champions', 0)}", "πŸ†", "#f59e0b"],
223
+ ["Healthy Customers", f"{risk_dist.get('Low', 0)}", "βœ…", "#06b6d4"]
224
  ]
225
 
226
+ return summary_html, kpi_data
227
 
228
  def train_churn_model(self):
229
+ """Train churn prediction model"""
230
  if self.df is None:
231
  return "No data available. Please upload a CSV file first.", None
232
 
 
244
  'first_order', 'last_order']
245
 
246
  customer_features['std_amount'].fillna(0, inplace=True)
247
+
248
  customer_features['customer_lifetime'] = (customer_features['last_order'] - customer_features['first_order']).dt.days
249
  customer_features['customer_lifetime'].fillna(0, inplace=True)
250
 
251
+ if 'churn_label' not in self.df.columns:
252
+ customer_features['churn_label'] = (customer_features['recency_days'] > 90).astype(int)
253
+ else:
254
+ churn_labels = self.df.groupby('customer_id')['churn_label'].first().reset_index()
255
+ customer_features = customer_features.merge(churn_labels, on='customer_id')
256
 
257
  feature_cols = ['recency_days', 'frequency', 'monetary', 'avg_amount', 'std_amount',
258
  'min_amount', 'max_amount', 'customer_lifetime']
 
266
  self.model.fit(X_train, y_train)
267
 
268
  y_pred = self.model.predict(X_test)
269
+ y_pred_proba = self.model.predict_proba(X_test)[:, 1]
270
 
271
  self.feature_importance = pd.DataFrame({
272
  'feature': feature_cols,
 
277
  customer_features['churn_probability'] = all_predictions
278
  self.predictions = customer_features
279
 
280
+ accuracy = accuracy_score(y_test, y_pred)
281
+
282
  results_html = f"""
283
+ <div style="background: white; padding: 2rem; border-radius: 1rem; box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1); border: 1px solid #e5e7eb; margin-bottom: 2rem;">
284
  <div style="text-align: center; margin-bottom: 2rem;">
285
+ <div style="display: inline-block; padding: 1rem; background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); border-radius: 50%; margin-bottom: 1rem;">
286
+ <span style="font-size: 2rem;">πŸ€–</span>
287
  </div>
288
  <h3 style="font-size: 1.75rem; font-weight: bold; color: #1f2937; margin-bottom: 0.5rem;">
289
  Model Training Completed
 
293
 
294
  <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1.5rem; margin-bottom: 2rem;">
295
  <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 1.5rem; border-radius: 1rem; text-align: center; color: white;">
296
+ <div style="font-size: 2.5rem; font-weight: bold; margin-bottom: 0.5rem;">{accuracy:.1%}</div>
297
  <div style="font-size: 1rem; opacity: 0.9;">Model Accuracy</div>
298
  </div>
299
  <div style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); padding: 1.5rem; border-radius: 1rem; text-align: center; color: white;">
300
+ <div style="font-size: 2.5rem; font-weight: bold; margin-bottom: 0.5rem;">{len(feature_cols)}</div>
301
  <div style="font-size: 1rem; opacity: 0.9;">Features Used</div>
302
  </div>
303
  <div style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); padding: 1.5rem; border-radius: 1rem; text-align: center; color: white;">
304
+ <div style="font-size: 2.5rem; font-weight: bold; margin-bottom: 0.5rem;">{len(X_train)}</div>
305
  <div style="font-size: 1rem; opacity: 0.9;">Training Samples</div>
306
  </div>
307
  <div style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); padding: 1.5rem; border-radius: 1rem; text-align: center; color: white;">
308
+ <div style="font-size: 2.5rem; font-weight: bold; margin-bottom: 0.5rem;">{len(X_test)}</div>
309
  <div style="font-size: 1rem; opacity: 0.9;">Test Samples</div>
310
  </div>
311
  </div>
312
 
313
+ <div style="background: #f8fafc; padding: 1.5rem; border-radius: 1rem;">
314
+ <h4 style="font-weight: 700; color: #374151; margin-bottom: 1rem; font-size: 1.2rem;">Top Feature Importance</h4>
315
+ <div>
316
  {''.join([f'''<div style="display: flex; justify-content: space-between; align-items: center; padding: 1rem 0; border-bottom: 1px solid #e5e7eb;">
317
+ <span style="font-weight: 600; color: #374151; font-size: 1rem;">{row['feature'].replace('_', ' ').title()}</span>
318
+ <span style="background: linear-gradient(135deg, #3b82f6, #1d4ed8); color: white; padding: 0.5rem 1rem; border-radius: 2rem; font-size: 0.9rem; font-weight: 600;">
319
+ {row['importance']:.3f}
320
+ </span>
 
 
 
 
 
321
  </div>''' for _, row in self.feature_importance.head(5).iterrows()])}
322
  </div>
323
  </div>
 
330
  return f"Error training model: {str(e)}", None
331
 
332
  def create_model_performance_chart(self):
333
+ """Create model performance visualization"""
334
  if self.feature_importance is None:
335
  return None
336
 
 
342
  title='Feature Importance Analysis',
343
  labels={'importance': 'Importance Score', 'feature': 'Features'},
344
  color='importance',
345
+ color_continuous_scale='viridis'
346
  )
347
 
348
  fig.update_layout(
349
+ height=500,
350
  showlegend=False,
351
  plot_bgcolor='white',
352
  paper_bgcolor='white',
353
  title={
354
+ 'text': '<b>Feature Importance Analysis</b>',
355
  'x': 0.5,
356
  'xanchor': 'center',
357
+ 'font': {'size': 20, 'color': '#1f2937'}
358
  },
359
+ font=dict(family="Inter, system-ui, sans-serif", size=12),
360
  yaxis={'categoryorder': 'total ascending'},
361
+ xaxis=dict(gridcolor='#f1f5f9'),
362
+ yaxis_title=dict(font_size=14),
363
+ xaxis_title=dict(font_size=14)
364
  )
365
 
366
  return fig
367
 
368
  def create_visualizations(self):
369
+ """Create comprehensive modern visualizations"""
370
  if self.df is None:
371
  return None, None, None, None
372
 
 
378
  segment_data,
379
  values='Count',
380
  names='Segment',
381
+ title='<b>Customer Segment Distribution</b>',
382
  hole=0.4,
383
  color_discrete_sequence=['#6366f1', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899']
384
  )
385
+ fig1.update_traces(textposition='inside', textinfo='percent+label', textfont_size=13)
 
 
 
 
 
386
  fig1.update_layout(
387
+ height=450,
388
  showlegend=True,
389
+ title={'x': 0.5, 'xanchor': 'center', 'font': {'size': 20, 'color': '#1f2937'}},
390
+ font=dict(family="Inter, system-ui, sans-serif", size=12),
391
  paper_bgcolor='white',
392
  plot_bgcolor='white'
393
  )
 
405
  x='recency_days',
406
  y='frequency',
407
  size='monetary',
408
+ color='Segment',
409
+ title='<b>RFM Customer Behavior Matrix</b>',
410
  labels={
411
+ 'recency_days': 'Days Since Last Purchase',
412
+ 'frequency': 'Purchase Frequency',
413
+ 'monetary': 'Total Revenue'
414
  },
415
+ color_discrete_sequence=['#6366f1', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6'],
416
+ size_max=60
417
  )
418
  fig2.update_layout(
419
+ height=500,
420
+ title={'x': 0.5, 'xanchor': 'center', 'font': {'size': 20, 'color': '#1f2937'}},
421
+ font=dict(family="Inter, system-ui, sans-serif", size=12),
422
  paper_bgcolor='white',
423
  plot_bgcolor='white'
424
  )
 
429
  self.predictions,
430
  x='churn_probability',
431
  nbins=20,
432
+ title='<b>Churn Probability Distribution</b>',
433
  labels={'churn_probability': 'Churn Probability', 'count': 'Number of Customers'},
434
+ color_discrete_sequence=[COLORS['primary']]
435
  )
436
+ fig3.add_vline(x=0.5, line_dash="dash", line_color="#ef4444", line_width=2,
437
+ annotation_text="High Risk Threshold", annotation_position="top")
438
  else:
439
  risk_data = self.df.groupby('customer_id')['Churn_Risk'].first().value_counts().reset_index()
440
  risk_data.columns = ['Risk_Level', 'Count']
 
443
  risk_data,
444
  x='Risk_Level',
445
  y='Count',
446
+ title='<b>Customer Churn Risk Distribution</b>',
447
  color='Risk_Level',
448
  color_discrete_map=colors_map
449
  )
450
 
451
  fig3.update_layout(
452
+ height=450,
453
  showlegend=False,
454
+ title={'x': 0.5, 'xanchor': 'center', 'font': {'size': 20, 'color': '#1f2937'}},
455
+ font=dict(family="Inter, system-ui, sans-serif", size=12),
456
+ plot_bgcolor='white',
457
+ paper_bgcolor='white'
458
  )
459
 
460
  # 4. Revenue Trends
 
466
  monthly_revenue,
467
  x='order_month',
468
  y='amount',
469
+ title='<b>Monthly Revenue Trends</b>',
470
  labels={'amount': 'Revenue ($)', 'order_month': 'Month'},
471
  line_shape='spline'
472
  )
473
+ fig4.update_traces(line_color=COLORS['primary'], line_width=4, mode='lines+markers')
474
  fig4.update_layout(
475
+ height=450,
476
+ title={'x': 0.5, 'xanchor': 'center', 'font': {'size': 20, 'color': '#1f2937'}},
477
+ font=dict(family="Inter, system-ui, sans-serif", size=12),
 
478
  plot_bgcolor='white',
479
+ paper_bgcolor='white',
480
+ xaxis_tickangle=-45,
481
+ xaxis=dict(gridcolor='#f1f5f9'),
482
+ yaxis=dict(gridcolor='#f1f5f9')
483
  )
484
 
485
+ return fig1, fig2, fig3, fig4
486
+
487
+ def create_customer_table(self):
488
+ """Create modern customer segmentation table"""
489
+ if self.df is None:
490
+ return None
491
+
492
+ customer_summary = self.df.groupby('customer_id').agg({
493
+ 'Segment': 'first',
494
+ 'Churn_Risk': 'first',
495
+ 'recency_days': 'first',
496
+ 'frequency': 'first',
497
+ 'monetary': 'first',
498
+ 'amount': 'mean'
499
+ }).reset_index()
500
+
501
+ if self.predictions is not None:
502
+ customer_summary = customer_summary.merge(
503
+ self.predictions[['customer_id', 'churn_probability']],
504
+ on='customer_id',
505
+ how='left'
506
+ )
507
+ customer_summary['churn_probability'] = customer_summary['churn_probability'].fillna(0)
508
+ else:
509
+ customer_summary['churn_probability'] = 0.5
510
+
511
+ customer_summary['monetary'] = customer_summary['monetary'].round(2)
512
+ customer_summary['amount'] = customer_summary['amount'].round(2)
513
+ customer_summary['churn_probability'] = (customer_summary['churn_probability'] * 100).round(1)
514
+
515
+ customer_summary.columns = [
516
+ 'Customer ID', 'Segment', 'Risk Level', 'Recency (Days)',
517
+ 'Frequency', 'Total Spent ($)', 'Avg Order ($)', 'Churn Probability (%)'
518
+ ]
519
+
520
+ return customer_summary.head(50)
521
+
522
+ def generate_pdf_report(self):
523
+ """Generate comprehensive PDF report"""
524
+ if self.df is None:
525
+ return None
526
+
527
+ try:
528
+ buffer = io.BytesIO()
529
+ doc = SimpleDocTemplate(buffer, pagesize=A4, rightMargin=72, leftMargin=72,
530
+ topMargin=72, bottomMargin=18)
531
+
532
+ styles = getSampleStyleSheet()
533
+ title_style = ParagraphStyle(
534
+ 'CustomTitle',
535
+ parent=styles['Heading1'],
536
+ fontSize=24,
537
+ spaceAfter=30,
538
+ textColor=colors.HexColor('#6366f1'),
539
+ alignment=1
540
+ )
541
+
542
+ story = []
543
+
544
+ story.append(Paragraph("B2B Customer Analytics Report", title_style))
545
+ story.append(Spacer(1, 20))
546
+
547
+ story.append(Paragraph("Executive Summary", styles['Heading2']))
548
+
549
+ total_customers = self.df['customer_id'].nunique()
550
+ total_revenue = self.df['amount'].sum()
551
+ avg_order_value = self.df['amount'].mean()
552
+ high_risk_customers = len(self.df[self.df['Churn_Risk'] == 'High']['customer_id'].unique())
553
+
554
+ summary_text = f"""
555
+ This comprehensive analysis examines {total_customers} B2B customers with total revenue of ${total_revenue:,.2f}.
556
+ The average order value stands at ${avg_order_value:.2f}, indicating healthy transaction volumes.
557
+
558
+ Critical findings reveal {high_risk_customers} customers at high risk of churning, representing significant revenue exposure.
559
+ Our machine learning model achieved high accuracy in predicting customer churn, enabling proactive retention strategies.
560
+
561
+ The customer segmentation analysis identifies distinct behavioral patterns, with Champions showing the highest lifetime value
562
+ and lowest churn risk, while At Risk customers require immediate intervention to prevent revenue loss.
563
+ """
564
+
565
+ story.append(Paragraph(summary_text, styles['Normal']))
566
+ story.append(Spacer(1, 20))
567
+
568
+ story.append(Paragraph("Key Performance Indicators", styles['Heading2']))
569
+
570
+ segment_dist = self.df.groupby('customer_id')['Segment'].first().value_counts()
571
+ risk_dist = self.df.groupby('customer_id')['Churn_Risk'].first().value_counts()
572
+
573
+ metrics_data = [
574
+ ['Metric', 'Value', 'Status'],
575
+ ['Total Customers', f"{total_customers:,}", 'Baseline'],
576
+ ['Total Revenue', f"${total_revenue:,.2f}", 'Strong'],
577
+ ['Average Order Value', f"${avg_order_value:.2f}", 'Healthy'],
578
+ ['Champions', f"{segment_dist.get('Champions', 0)}", 'Retain'],
579
+ ['At Risk Customers', f"{segment_dist.get('At Risk', 0)}", 'Action Required'],
580
+ ['High Risk Churn', f"{risk_dist.get('High', 0)}", 'Critical'],
581
+ ['Low Risk Churn', f"{risk_dist.get('Low', 0)}", 'Stable']
582
+ ]
583
+
584
+ metrics_table = Table(metrics_data, colWidths=[2*inch, 1.5*inch, 1.5*inch])
585
+ metrics_table.setStyle(TableStyle([
586
+ ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#6366f1')),
587
+ ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
588
+ ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
589
+ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
590
+ ('FONTSIZE', (0, 0), (-1, 0), 12),
591
+ ('BOTTOMPADDING', (0, 0), (-1, 0), 12),
592
+ ('BACKGROUND', (0, 1), (-1, -1), colors.beige),
593
+ ('GRID', (0, 0), (-1, -1), 1, colors.black),
594
+ ('FONTSIZE', (0, 1), (-1, -1), 10),
595
+ ('VALIGN', (0, 0), (-1, -1), 'MIDDLE')
596
+ ]))
597
+
598
+ story.append(metrics_table)
599
+ story.append(Spacer(1, 20))
600
+
601
+ story.append(Paragraph("Strategic Recommendations", styles['Heading2']))
602
+
603
+ recommendations_text = """
604
+ Based on our comprehensive analysis, we recommend the following strategic actions:
605
+
606
+ 1. IMMEDIATE ACTIONS (0-30 days):
607
+ β€’ Contact all high-risk customers personally
608
+ β€’ Offer retention incentives to at-risk segments
609
+ β€’ Implement automated early warning system
610
+
611
+ 2. SHORT-TERM INITIATIVES (1-3 months):
612
+ β€’ Develop targeted marketing campaigns by segment
613
+ β€’ Launch loyalty program for Champions
614
+ β€’ Create win-back campaigns for lost customers
615
+
616
+ 3. LONG-TERM STRATEGY (3-12 months):
617
+ β€’ Invest in customer success programs
618
+ β€’ Develop predictive analytics capabilities
619
+ β€’ Build comprehensive customer health scoring
620
+ β€’ Implement continuous model monitoring and improvement
621
+ """
622
+
623
+ story.append(Paragraph(recommendations_text, styles['Normal']))
624
+ story.append(Spacer(1, 20))
625
+
626
+ story.append(Paragraph(f"Report generated on {datetime.now().strftime('%B %d, %Y at %I:%M %p')}",
627
+ styles['Normal']))
628
+ story.append(Paragraph("B2B Customer Analytics Platform - Enterprise Edition",
629
+ styles['Normal']))
630
+
631
+ doc.build(story)
632
+ pdf_bytes = buffer.getvalue()
633
+ buffer.close()
634
+
635
+ return pdf_bytes
636
+
637
+ except Exception as e:
638
+ print(f"Error generating PDF report: {str(e)}")
639
+ return None
640
+
641
+ def get_customer_insights(self, customer_id):
642
+ """Get detailed insights for a specific customer"""
643
+ if self.df is None:
644
+ return "No data available"
645
+
646
+ customer_data = self.df[self.df['customer_id'] == customer_id]
647
+ if customer_data.empty:
648
+ return f"Customer {customer_id} not found"
649
+
650
+ total_orders = len(customer_data)
651
+ total_spent = customer_data['amount'].sum()
652
+ avg_order_value = customer_data['amount'].mean()
653
+ first_order = customer_data['order_date'].min()
654
+ last_order = customer_data['order_date'].max()
655
+ segment = customer_data['Segment'].iloc[0]
656
+ risk_level = customer_data['Churn_Risk'].iloc[0]
657
+ recency = customer_data['recency_days'].iloc[0]
658
+
659
+ churn_prob = 0.5
660
+ if self.predictions is not None:
661
+ pred_data = self.predictions[self.predictions['customer_id'] == customer_id]
662
+ if not pred_data.empty:
663
+ churn_prob = pred_data['churn_probability'].iloc[0]
664
+
665
+ insights_html = f"""
666
+ <div style="background: white; padding: 2rem; border-radius: 1rem; box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1); margin-bottom: 2rem;">
667
+ <div style="text-align: center; margin-bottom: 2rem;">
668
+ <div style="display: inline-block; padding: 1.5rem; background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); border-radius: 50%; margin-bottom: 1rem;">
669
+ <span style="font-size: 2rem; color: white;">πŸ“Š</span>
670
+ </div>
671
+ <h3 style="color: #1f2937; font-size: 1.75rem; font-weight: bold; margin-bottom: 0.5rem;">
672
+ Customer Profile: {customer_id}
673
+ </h3>
674
+ <p style="color: #6b7280; font-size: 1.1rem;">Comprehensive Customer Intelligence Report</p>
675
+ </div>
676
+
677
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1.5rem; margin-bottom: 2rem;">
678
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 1.5rem; border-radius: 1rem; color: white; text-align: center;">
679
+ <h4 style="font-size: 0.9rem; opacity: 0.9; margin-bottom: 0.5rem; font-weight: 600;">CUSTOMER SEGMENT</h4>
680
+ <div style="font-size: 1.5rem; font-weight: bold;">{segment}</div>
681
+ </div>
682
+ <div style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); padding: 1.5rem; border-radius: 1rem; color: white; text-align: center;">
683
+ <h4 style="font-size: 0.9rem; opacity: 0.9; margin-bottom: 0.5rem; font-weight: 600;">CHURN RISK</h4>
684
+ <div style="font-size: 1.5rem; font-weight: bold;">{risk_level}</div>
685
+ </div>
686
+ <div style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); padding: 1.5rem; border-radius: 1rem; color: white; text-align: center;">
687
+ <h4 style="font-size: 0.9rem; opacity: 0.9; margin-bottom: 0.5rem; font-weight: 600;">CHURN PROBABILITY</h4>
688
+ <div style="font-size: 1.5rem; font-weight: bold;">{churn_prob:.1%}</div>
689
+ </div>
690
+ </div>
691
+
692
+ <div style="background: #f8fafc; padding: 2rem; border-radius: 1rem; margin-bottom: 2rem;">
693
+ <h4 style="color: #374151; font-weight: 700; margin-bottom: 1.5rem; font-size: 1.3rem;">Transaction Analytics</h4>
694
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 2rem;">
695
+ <div>
696
+ <div style="font-size: 0.875rem; color: #6b7280; font-weight: 600; margin-bottom: 0.5rem;">Total Orders</div>
697
+ <div style="font-size: 2rem; font-weight: bold; color: #1f2937;">{total_orders}</div>
698
+ </div>
699
+ <div>
700
+ <div style="font-size: 0.875rem; color: #6b7280; font-weight: 600; margin-bottom: 0.5rem;">Total Spent</div>
701
+ <div style="font-size: 2rem; font-weight: bold; color: #1f2937;">${total_spent:,.2f}</div>
702
+ </div>
703
+ <div>
704
+ <div style="font-size: 0.875rem; color: #6b7280; font-weight: 600; margin-bottom: 0.5rem;">Avg Order Value</div>
705
+ <div style="font-size: 2rem; font-weight: bold; color: #1f2937;">${avg_order_value:.2f}</div>
706
+ </div>
707
+ <div>
708
+ <div style="font-size: 0.875rem; color: #6b7280; font-weight: 600; margin-bottom: 0.5rem;">Days Since Last Order</div>
709
+ <div style="font-size: 2rem; font-weight: bold; color: #1f2937;">{recency}</div>
710
+ </div>
711
+ </div>
712
+ </div>
713
+
714
+ <div style="background: linear-gradient(135deg, #f0f9ff, #e0f2fe); border-left: 4px solid #3b82f6; padding: 1.5rem; border-radius: 0.5rem;">
715
+ <h4 style="color: #1e40af; font-weight: 700; margin-bottom: 1rem; font-size: 1.2rem;">Strategic Recommendations</h4>
716
+ <p style="color: #1f2937; margin: 0; font-size: 1rem; line-height: 1.6;">
717
+ {self._get_customer_recommendations(segment, risk_level, churn_prob, recency)}
718
+ </p>
719
+ </div>
720
+ </div>
721
+ """
722
+
723
+ return insights_html
724
+
725
+ def _get_customer_recommendations(self, segment, risk_level, churn_prob, recency):
726
+ """Generate personalized recommendations based on customer profile"""
727
+ recommendations = []
728
+
729
+ if risk_level == 'High' or churn_prob > 0.7:
730
+ recommendations.append("🚨 URGENT: Personal outreach required within 24 hours")
731
+ recommendations.append("πŸ’° Offer retention incentive (discount/upgrade)")
732
+ recommendations.append("πŸ“ž Schedule executive-level call")
733
+ elif risk_level == 'Medium':
734
+ recommendations.append("πŸ“§ Send personalized re-engagement campaign")
735
+ recommendations.append("🎯 Offer targeted product recommendations")
736
+
737
+ if segment == 'Champions':
738
+ recommendations.append("πŸ† Invite to VIP program or advisory board")
739
+ recommendations.append("πŸ”„ Cross-sell premium services")
740
+ elif segment == 'At Risk':
741
+ recommendations.append("⚠️ Proactive customer success intervention")
742
+ recommendations.append("πŸ“Š Conduct health check survey")
743
+ elif segment == 'New Customers':
744
+ recommendations.append("πŸŽ‰ Deploy onboarding campaign")
745
+ recommendations.append("πŸ“š Provide educational resources")
746
+
747
+ if recency > 60:
748
+ recommendations.append("πŸ”„ Win-back campaign with special offer")
749
+
750
+ return " β€’ ".join(recommendations) if recommendations else "Continue monitoring customer engagement patterns."
751
+
752
+
753
+ def create_gradio_interface():
754
+ """Create the modern Gradio interface for the B2B Customer Analytics platform"""
755
+
756
+ analytics = B2BCustomerAnalytics()
757
+
758
+ def load_data(file):
759
+ if file is None:
760
+ return "Please upload a CSV file", None, None, None
761
+ result = analytics.load_and_process_data(file)
762
+ return result
763
+
764
+ def train_model():
765
+ result = analytics.train_churn_model()
766
+ return result
767
+
768
+ def create_charts():
769
+ return analytics.create_visualizations()
770
+
771
+ def get_customer_table():
772
+ return analytics.create_customer_table()
773
+
774
+ def generate_report():
775
+ pdf_bytes = analytics.generate_pdf_report()
776
+ if pdf_bytes:
777
+ return pdf_bytes
778
+ return None
779
+
780
+ def get_insights(customer_id):
781
+ if not customer_id:
782
+ return "Please enter a customer ID"
783
+ return analytics.get_customer_insights(customer_id)
784
+
785
+ # Custom CSS for modern styling
786
+ custom_css = """
787
+ .gradio-container {
788
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;
789
+ max-width: 1400px !important;
790
+ margin: 0 auto !important;
791
+ }
792
+ .main-header {
793
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%) !important;
794
+ padding: 2rem !important;
795
+ border-radius: 1rem !important;
796
+ color: white !important;
797
+ text-align: center !important;
798
+ margin-bottom: 2rem !important;
799
+ }
800
+ .tab-nav {
801
+ border-radius: 1rem !important;
802
+ background: white !important;
803
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1) !important;
804
+ }
805
+ .block {
806
+ border-radius: 1rem !important;
807
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1) !important;
808
+ border: 1px solid #e5e7eb !important;
809
+ }
810
+ """
811
+
812
+ with gr.Blocks(
813
+ theme=gr.themes.Soft(
814
+ primary_hue="blue",
815
+ secondary_hue="purple",
816
+ neutral_hue="slate"
817
+ ),
818
+ title="B2B Customer Analytics Platform",
819
+ css=custom_css
820
+ ) as demo:
821
+
822
+ gr.HTML("""
823
+ <div style="background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); padding: 3rem 2rem; border-radius: 1rem; color: white; text-align: center; margin-bottom: 2rem;">
824
+ <div style="display: inline-block; padding: 1rem; background: rgba(255, 255, 255, 0.2); border-radius: 50%; margin-bottom: 1rem;">
825
+ <span style="font-size: 3rem;">🏒</span>
826
+ </div>
827
+ <h1 style="font-size: 3rem; font-weight: 800; margin-bottom: 0.5rem; text-shadow: 0 2px 4px rgba(0,0,0,0.1);">
828
+ B2B Customer Analytics Platform
829
+ </h1>
830
+ <p style="font-size: 1.3rem; opacity: 0.95; font-weight: 500;">
831
+ Enterprise Customer Health Monitoring & Churn Prediction System
832
+ </p>
833
+ <div style="margin-top: 1.5rem; font-size: 0.95rem; opacity: 0.8;">
834
+ Powered by Advanced Machine Learning & Predictive Analytics
835
+ </div>
836
+ </div>
837
+ """)
838
+
839
+ with gr.Tabs(elem_classes="tab-nav"):
840
+
841
+ with gr.Tab("πŸ“Š Data Upload & Dashboard", elem_id="data-tab"):
842
+ with gr.Row():
843
+ with gr.Column(scale=1):
844
+ file_input = gr.File(
845
+ label="Upload Customer Data CSV",
846
+ file_types=[".csv"],
847
+ elem_classes="block"
848
+ )
849
+ load_btn = gr.Button(
850
+ "Load & Process Data",
851
+ variant="primary",
852
+ size="lg",
853
+ elem_classes="block"
854
+ )
855
+ load_status = gr.HTML()
856
+
857
+ summary_display = gr.HTML()
858
+ data_preview = gr.DataFrame(
859
+ label="Data Preview",
860
+ max_rows=10,
861
+ elem_classes="block"
862
+ )
863
+
864
+ with gr.Tab("🎯 Customer Segmentation Analysis", elem_id="segmentation-tab"):
865
+ with gr.Row():
866
+ with gr.Column():
867
+ segment_chart = gr.Plot(label="Customer Segments Distribution")
868
+ with gr.Column():
869
+ rfm_chart = gr.Plot(label="RFM Behavior Matrix")
870
+
871
+ customer_table = gr.DataFrame(
872
+ label="Customer Segmentation Details",
873
+ elem_classes="block"
874
+ )
875
+
876
+ with gr.Tab("πŸ€– AI-Powered Churn Prediction", elem_id="churn-tab"):
877
+ with gr.Column():
878
+ train_btn = gr.Button(
879
+ "Train Churn Prediction Model",
880
+ variant="primary",
881
+ size="lg",
882
+ elem_classes="block"
883
+ )
884
+
885
+ model_results = gr.HTML()
886
+
887
+ with gr.Row():
888
+ with gr.Column():
889
+ performance_chart = gr.Plot(label="Feature Importance Analysis")
890
+ with gr.Column():
891
+ churn_chart = gr.Plot(label="Churn Risk Distribution")
892
+
893
+ with gr.Tab("πŸ’° Revenue Analytics", elem_id="revenue-tab"):
894
+ revenue_chart = gr.Plot(
895
+ label="Monthly Revenue Trends",
896
+ elem_classes="block"
897
+ )
898
+
899
+ with gr.Tab("πŸ” Customer Intelligence", elem_id="insights-tab"):
900
+ with gr.Row():
901
+ with gr.Column(scale=3):
902
+ customer_id_input = gr.Textbox(
903
+ label="Customer ID Lookup",
904
+ placeholder="Enter customer ID (e.g., CUST001)",
905
+ elem_classes="block"
906
+ )
907
+ with gr.Column(scale=1):
908
+ insights_btn = gr.Button(
909
+ "Get Customer Profile",
910
+ variant="primary",
911
+ elem_classes="block"
912
+ )
913
+
914
+ customer_insights = gr.HTML()
915
+
916
+ with gr.Tab("πŸ“„ Executive Reports", elem_id="reports-tab"):
917
+ with gr.Column():
918
+ gr.HTML("""
919
+ <div style="background: linear-gradient(135deg, #f0f9ff, #e0f2fe); padding: 2rem; border-radius: 1rem; margin-bottom: 2rem; border-left: 4px solid #3b82f6;">
920
+ <h3 style="color: #1e40af; margin-bottom: 1rem; font-size: 1.5rem; font-weight: 700;">πŸ“‹ Comprehensive Analytics Report</h3>
921
+ <p style="color: #374151; margin-bottom: 1.5rem; font-size: 1.1rem;">Generate a complete executive summary with insights, recommendations, and strategic action items.</p>
922
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem;">
923
+ <div style="font-size: 0.9rem; color: #6b7280;">βœ“ Executive Summary</div>
924
+ <div style="font-size: 0.9rem; color: #6b7280;">βœ“ Customer Segmentation</div>
925
+ <div style="font-size: 0.9rem; color: #6b7280;">βœ“ Churn Risk Assessment</div>
926
+ <div style="font-size: 0.9rem; color: #6b7280;">βœ“ Revenue Analytics</div>
927
+ <div style="font-size: 0.9rem; color: #6b7280;">βœ“ Strategic Recommendations</div>
928
+ <div style="font-size: 0.9rem; color: #6b7280;">βœ“ Model Performance Metrics</div>
929
+ </div>
930
+ </div>
931
+ """)
932
+
933
+ report_btn = gr.Button(
934
+ "Generate Executive Report",
935
+ variant="primary",
936
+ size="lg",
937
+ elem_classes="block"
938
+ )
939
+
940
+ report_download = gr.File(
941
+ label="Download PDF Report",
942
+ elem_classes="block"
943
+ )
944
+
945
+ # Event handlers
946
+ load_btn.click(
947
+ fn=load_data,
948
+ inputs=[file_input],
949
+ outputs=[load_status, summary_display, data_preview, gr.HTML()]
950
+ )
951
+
952
+ train_btn.click(
953
+ fn=train_model,
954
+ outputs=[model_results, performance_chart]
955
+ )
956
+
957
+ load_btn.click(
958
+ fn=create_charts,
959
+ outputs=[segment_chart, rfm_chart, churn_chart, revenue_chart]
960
+ )
961
+
962
+ load_btn.click(
963
+ fn=get_customer_table,
964
+ outputs=[customer_table]
965
+ )
966
+
967
+ insights_btn.click(
968
+ fn=get_insights,
969
+ inputs=[customer_id_input],
970
+ outputs=[customer_insights]
971
+ )
972
+
973
+ report_btn.click(
974
+ fn=generate_report,
975
+ outputs=[report_download]
976
+ )
977
+
978
+ return demo
979
+
980
+ if __name__ == "__main__":
981
+ demo = create_gradio_interface()
982
+ demo.launch(
983
+ share=True,
984
+ server_name="0.0.0.0",
985
+ server_port=7860,
986
+ show_error=True
987
+ )