junaid17 commited on
Commit
3d14250
·
verified ·
1 Parent(s): 8c927aa

Upload 8 files

Browse files
analysis_model.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
app.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from predict_helper import predict
3
+ from pydantic import BaseModel, Field
4
+ from fastapi.middleware.cors import CORSMiddleware
5
+
6
+ app = FastAPI(title='Customer Segmentation', version='1.0')
7
+
8
+ # Add CORS middleware
9
+ app.add_middleware(
10
+ CORSMiddleware,
11
+ allow_origins=["*"], # Allows all origins
12
+ allow_credentials=True,
13
+ allow_methods=["*"], # Allows all methods
14
+ allow_headers=["*"], # Allows all headers
15
+ )
16
+
17
+
18
+ class BaseInput(BaseModel):
19
+ Age : int = Field(..., ge=18, le=100, description="Customer age between 18 and 100")
20
+ Income : int = Field(..., ge=0, le=200000, description="Income between 0 and 200000")
21
+ Total_Spendings : int = Field(..., ge=0, le=5000, description="Total spendings (sum of purchases)")
22
+ NumWebPurchases : int = Field(..., ge=0, le=100, description="Number of web purchases")
23
+ NumStorePurchases : int = Field(..., ge=0, le=100, description="Number of store purchases")
24
+ NumWebVisitsMonth : int = Field(..., ge=0, le=50, description="Number of web visits per month")
25
+ Recency : int = Field(..., ge=0, le=365, description="Recency (days since last purchase)")
26
+
27
+
28
+ class BaseOutput(BaseModel):
29
+ cluster_id : int
30
+ cluster_name : str
31
+ description : str
32
+ recommendation : str
33
+
34
+
35
+ @app.get('/')
36
+ def Status():
37
+ return {'message' : 'The api server is live and working'}
38
+
39
+
40
+ @app.post('/predict', response_model=BaseOutput)
41
+ def predict_segment(input_data: BaseInput):
42
+ try:
43
+ result = predict(
44
+ age=input_data.Age,
45
+ income=input_data.Income,
46
+ total_spending=input_data.Total_Spendings,
47
+ num_web_purchases=input_data.NumWebPurchases,
48
+ num_store_purchases=input_data.NumStorePurchases,
49
+ num_web_visits=input_data.NumWebVisitsMonth,
50
+ recency=input_data.Recency
51
+ )
52
+ return result
53
+ except Exception as e:
54
+ raise HTTPException(status_code=400, detail=f"Error while predicting the output: {e}")
artifacts/kmeans.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6ed70d46d041c4c0a154ddb5103f82f7fb599b0e4770989295e52a9b234692a7
3
+ size 9815
artifacts/scaler.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:191d1c23878f3171c9c62d62e08a3b584595a5c4a756e715956cb40c1c3e7860
3
+ size 1119
data/customer_segmentation.csv ADDED
The diff for this file is too large to render. See raw diff
 
index.html ADDED
@@ -0,0 +1,563 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Customer Segmentation API</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ :root {
15
+ --dark-gray: #1a1a1a;
16
+ --darker-gray: #0d0d0d;
17
+ --light-gray: #2d2d2d;
18
+ --accent: #4a90e2;
19
+ --accent-hover: #357abd;
20
+ --text: #e0e0e0;
21
+ --text-secondary: #a0a0a0;
22
+ --success: #4caf50;
23
+ --error: #f44336;
24
+ }
25
+
26
+ body {
27
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
28
+ background: linear-gradient(135deg, var(--darker-gray), var(--dark-gray));
29
+ color: var(--text);
30
+ min-height: 100vh;
31
+ padding: 20px;
32
+ line-height: 1.6;
33
+ }
34
+
35
+ .container {
36
+ max-width: 1200px;
37
+ margin: 0 auto;
38
+ }
39
+
40
+ .header {
41
+ text-align: center;
42
+ margin-bottom: 40px;
43
+ animation: fadeInDown 1s ease-out;
44
+ }
45
+
46
+ .header h1 {
47
+ font-size: 2.5rem;
48
+ margin-bottom: 10px;
49
+ background: linear-gradient(45deg, var(--accent), #66b3ff);
50
+ -webkit-background-clip: text;
51
+ -webkit-text-fill-color: transparent;
52
+ background-clip: text;
53
+ }
54
+
55
+ .header p {
56
+ color: var(--text-secondary);
57
+ font-size: 1.1rem;
58
+ }
59
+
60
+ .main-content {
61
+ display: grid;
62
+ grid-template-columns: 1fr;
63
+ gap: 30px;
64
+ }
65
+
66
+ @media (min-width: 768px) {
67
+ .main-content {
68
+ grid-template-columns: 1fr 1fr;
69
+ }
70
+ }
71
+
72
+ .form-section, .result-section {
73
+ background: var(--light-gray);
74
+ padding: 30px;
75
+ border-radius: 15px;
76
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
77
+ animation: slideInUp 0.8s ease-out;
78
+ }
79
+
80
+ .form-section h2, .result-section h2 {
81
+ margin-bottom: 25px;
82
+ color: var(--accent);
83
+ font-size: 1.8rem;
84
+ }
85
+
86
+ .form-group {
87
+ margin-bottom: 20px;
88
+ }
89
+
90
+ .form-group label {
91
+ display: block;
92
+ margin-bottom: 8px;
93
+ color: var(--text);
94
+ font-weight: 500;
95
+ }
96
+
97
+ .form-group input {
98
+ width: 100%;
99
+ padding: 12px 15px;
100
+ border: 2px solid #404040;
101
+ border-radius: 8px;
102
+ background: var(--darker-gray);
103
+ color: var(--text);
104
+ font-size: 1rem;
105
+ transition: all 0.3s ease;
106
+ }
107
+
108
+ .form-group input:focus {
109
+ outline: none;
110
+ border-color: var(--accent);
111
+ box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.2);
112
+ }
113
+
114
+ .form-group input:hover {
115
+ border-color: #5a9fe4;
116
+ }
117
+
118
+ .submit-btn {
119
+ width: 100%;
120
+ padding: 15px;
121
+ background: linear-gradient(45deg, var(--accent), #66b3ff);
122
+ color: white;
123
+ border: none;
124
+ border-radius: 8px;
125
+ font-size: 1.1rem;
126
+ font-weight: 600;
127
+ cursor: pointer;
128
+ transition: all 0.3s ease;
129
+ margin-top: 10px;
130
+ }
131
+
132
+ .submit-btn:hover {
133
+ transform: translateY(-2px);
134
+ box-shadow: 0 5px 15px rgba(74, 144, 226, 0.4);
135
+ }
136
+
137
+ .submit-btn:active {
138
+ transform: translateY(0);
139
+ }
140
+
141
+ .submit-btn:disabled {
142
+ background: #555;
143
+ cursor: not-allowed;
144
+ transform: none;
145
+ box-shadow: none;
146
+ }
147
+
148
+ .result-card {
149
+ background: var(--darker-gray);
150
+ padding: 25px;
151
+ border-radius: 10px;
152
+ border-left: 4px solid var(--accent);
153
+ margin-bottom: 20px;
154
+ animation: fadeIn 0.6s ease-out;
155
+ }
156
+
157
+ .result-card h3 {
158
+ color: var(--accent);
159
+ margin-bottom: 10px;
160
+ font-size: 1.3rem;
161
+ }
162
+
163
+ .result-card p {
164
+ color: var(--text-secondary);
165
+ margin-bottom: 5px;
166
+ }
167
+
168
+ .loading {
169
+ text-align: center;
170
+ padding: 20px;
171
+ display: none;
172
+ }
173
+
174
+ .spinner {
175
+ border: 4px solid rgba(255, 255, 255, 0.3);
176
+ border-radius: 50%;
177
+ border-top: 4px solid var(--accent);
178
+ width: 40px;
179
+ height: 40px;
180
+ animation: spin 1s linear infinite;
181
+ margin: 0 auto 15px;
182
+ }
183
+
184
+ .error {
185
+ background: rgba(244, 67, 54, 0.1);
186
+ border: 1px solid var(--error);
187
+ color: var(--error);
188
+ padding: 15px;
189
+ border-radius: 8px;
190
+ margin: 15px 0;
191
+ display: none;
192
+ }
193
+
194
+ .success {
195
+ background: rgba(76, 175, 80, 0.1);
196
+ border: 1px solid var(--success);
197
+ color: var(--success);
198
+ padding: 15px;
199
+ border-radius: 8px;
200
+ margin: 15px 0;
201
+ display: none;
202
+ }
203
+
204
+ @keyframes fadeInDown {
205
+ from {
206
+ opacity: 0;
207
+ transform: translateY(-30px);
208
+ }
209
+ to {
210
+ opacity: 1;
211
+ transform: translateY(0);
212
+ }
213
+ }
214
+
215
+ @keyframes slideInUp {
216
+ from {
217
+ opacity: 0;
218
+ transform: translateY(50px);
219
+ }
220
+ to {
221
+ opacity: 1;
222
+ transform: translateY(0);
223
+ }
224
+ }
225
+
226
+ @keyframes fadeIn {
227
+ from {
228
+ opacity: 0;
229
+ }
230
+ to {
231
+ opacity: 1;
232
+ }
233
+ }
234
+
235
+ @keyframes spin {
236
+ 0% { transform: rotate(0deg); }
237
+ 100% { transform: rotate(360deg); }
238
+ }
239
+
240
+ @keyframes pulse {
241
+ 0% { transform: scale(1); }
242
+ 50% { transform: scale(1.05); }
243
+ 100% { transform: scale(1); }
244
+ }
245
+
246
+ .pulse {
247
+ animation: pulse 2s infinite;
248
+ }
249
+
250
+ .mobile-only {
251
+ display: block;
252
+ }
253
+
254
+ @media (min-width: 768px) {
255
+ .mobile-only {
256
+ display: none;
257
+ }
258
+ }
259
+
260
+ .desktop-only {
261
+ display: none;
262
+ }
263
+
264
+ @media (min-width: 768px) {
265
+ .desktop-only {
266
+ display: block;
267
+ }
268
+ }
269
+
270
+ .form-grid {
271
+ display: grid;
272
+ grid-template-columns: 1fr;
273
+ gap: 15px;
274
+ }
275
+
276
+ @media (min-width: 768px) {
277
+ .form-grid {
278
+ grid-template-columns: 1fr 1fr;
279
+ }
280
+ }
281
+
282
+ .input-pair {
283
+ display: grid;
284
+ grid-template-columns: 1fr 1fr;
285
+ gap: 15px;
286
+ }
287
+
288
+ @media (max-width: 767px) {
289
+ .input-pair {
290
+ grid-template-columns: 1fr;
291
+ }
292
+ }
293
+
294
+ .result-highlight {
295
+ background: rgba(74, 144, 226, 0.1);
296
+ padding: 15px;
297
+ border-radius: 8px;
298
+ margin: 10px 0;
299
+ border: 1px solid var(--accent);
300
+ }
301
+
302
+ .result-title {
303
+ font-size: 1.2rem;
304
+ color: var(--accent);
305
+ margin-bottom: 5px;
306
+ }
307
+
308
+ .result-content {
309
+ color: var(--text);
310
+ }
311
+
312
+ .no-result {
313
+ text-align: center;
314
+ color: var(--text-secondary);
315
+ padding: 40px 20px;
316
+ font-style: italic;
317
+ }
318
+
319
+ .animate-in {
320
+ animation: fadeIn 0.5s ease-out forwards;
321
+ }
322
+
323
+ .animate-out {
324
+ animation: fadeOut 0.3s ease-out forwards;
325
+ }
326
+
327
+ @keyframes fadeOut {
328
+ from { opacity: 1; }
329
+ to { opacity: 0; }
330
+ }
331
+
332
+ .slide-up {
333
+ animation: slideUp 0.5s ease-out;
334
+ }
335
+
336
+ @keyframes slideUp {
337
+ from {
338
+ transform: translateY(100%);
339
+ opacity: 0;
340
+ }
341
+ to {
342
+ transform: translateY(0);
343
+ opacity: 1;
344
+ }
345
+ }
346
+ </style>
347
+ </head>
348
+ <body>
349
+ <div class="container">
350
+ <div class="header">
351
+ <h1>Customer Segmentation API</h1>
352
+ <p>Enter customer data to predict their segment and get personalized recommendations</p>
353
+ </div>
354
+
355
+ <div class="main-content">
356
+ <div class="form-section">
357
+ <h2>Customer Data</h2>
358
+ <form id="predictionForm">
359
+ <div class="form-grid">
360
+ <div class="form-group">
361
+ <label for="age">Age</label>
362
+ <input type="number" id="age" name="age" min="18" max="100" required placeholder="18-100">
363
+ </div>
364
+ <div class="form-group">
365
+ <label for="income">Income</label>
366
+ <input type="number" id="income" name="income" min="0" max="200000" required placeholder="0-200000">
367
+ </div>
368
+ </div>
369
+
370
+ <div class="form-group">
371
+ <label for="totalSpendings">Total Spendings</label>
372
+ <input type="number" id="totalSpendings" name="totalSpendings" min="0" max="5000" required placeholder="0-5000">
373
+ </div>
374
+
375
+ <div class="input-pair">
376
+ <div class="form-group">
377
+ <label for="webPurchases">Web Purchases</label>
378
+ <input type="number" id="webPurchases" name="webPurchases" min="0" max="100" required placeholder="0-100">
379
+ </div>
380
+ <div class="form-group">
381
+ <label for="storePurchases">Store Purchases</label>
382
+ <input type="number" id="storePurchases" name="storePurchases" min="0" max="100" required placeholder="0-100">
383
+ </div>
384
+ </div>
385
+
386
+ <div class="input-pair">
387
+ <div class="form-group">
388
+ <label for="webVisits">Web Visits/Month</label>
389
+ <input type="number" id="webVisits" name="webVisits" min="0" max="50" required placeholder="0-50">
390
+ </div>
391
+ <div class="form-group">
392
+ <label for="recency">Recency (days)</label>
393
+ <input type="number" id="recency" name="recency" min="0" max="365" required placeholder="0-365">
394
+ </div>
395
+ </div>
396
+
397
+ <button type="submit" class="submit-btn pulse" id="predictBtn">
398
+ Predict Customer Segment
399
+ </button>
400
+ </form>
401
+
402
+ <div class="loading" id="loading">
403
+ <div class="spinner"></div>
404
+ <p>Analyzing customer data...</p>
405
+ </div>
406
+
407
+ <div class="error" id="error"></div>
408
+ <div class="success" id="success"></div>
409
+ </div>
410
+
411
+ <div class="result-section">
412
+ <h2>Prediction Result</h2>
413
+ <div id="resultContent">
414
+ <div class="no-result">
415
+ <p>Enter customer data and click "Predict Customer Segment" to see results</p>
416
+ </div>
417
+ </div>
418
+ </div>
419
+ </div>
420
+ </div>
421
+
422
+ <script>
423
+ document.addEventListener('DOMContentLoaded', function() {
424
+ const form = document.getElementById('predictionForm');
425
+ const predictBtn = document.getElementById('predictBtn');
426
+ const loading = document.getElementById('loading');
427
+ const error = document.getElementById('error');
428
+ const success = document.getElementById('success');
429
+ const resultContent = document.getElementById('resultContent');
430
+
431
+ // Sample result template
432
+ const resultTemplate = {
433
+ "cluster_id": 3,
434
+ "cluster_name": "Active Online-Focused Shoppers",
435
+ "description": "High income, high spending, shops frequently both online and in-store—with strongest activity on web.",
436
+ "recommendation": "Offer premium bundles, omnichannel loyalty rewards (e.g., buy online, pick up in-store + bonus points), and personalized cross-channel recommendations."
437
+ };
438
+
439
+ form.addEventListener('submit', async function(e) {
440
+ e.preventDefault();
441
+
442
+ // Show loading
443
+ loading.style.display = 'block';
444
+ error.style.display = 'none';
445
+ success.style.display = 'none';
446
+
447
+ // Disable button during prediction
448
+ predictBtn.disabled = true;
449
+ predictBtn.textContent = 'Analyzing...';
450
+
451
+ try {
452
+ // Get form data
453
+ const formData = new FormData(form);
454
+ const inputData = {
455
+ Age: parseInt(formData.get('age')),
456
+ Income: parseInt(formData.get('income')),
457
+ Total_Spendings: parseInt(formData.get('totalSpendings')),
458
+ NumWebPurchases: parseInt(formData.get('webPurchases')),
459
+ NumStorePurchases: parseInt(formData.get('storePurchases')),
460
+ NumWebVisitsMonth: parseInt(formData.get('webVisits')),
461
+ Recency: parseInt(formData.get('recency'))
462
+ };
463
+
464
+ // Make API call
465
+ const response = await fetch('http://localhost:8000/predict', {
466
+ method: 'POST',
467
+ headers: {
468
+ 'Content-Type': 'application/json',
469
+ },
470
+ body: JSON.stringify(inputData)
471
+ });
472
+
473
+ if (!response.ok) {
474
+ throw new Error(`API error: ${response.status}`);
475
+ }
476
+
477
+ const result = await response.json();
478
+
479
+ // Display result
480
+ displayResult(result);
481
+ success.style.display = 'block';
482
+ success.textContent = 'Prediction successful!';
483
+
484
+ } catch (err) {
485
+ console.error('Prediction error:', err);
486
+ error.style.display = 'block';
487
+ error.textContent = `Error: ${err.message || 'Failed to get prediction'}`;
488
+
489
+ // Also show in result area
490
+ resultContent.innerHTML = `
491
+ <div class="error" style="display: block;">
492
+ <p>Failed to get prediction: ${err.message || 'Unknown error'}</p>
493
+ </div>
494
+ `;
495
+ } finally {
496
+ // Hide loading and re-enable button
497
+ loading.style.display = 'none';
498
+ predictBtn.disabled = false;
499
+ predictBtn.textContent = 'Predict Customer Segment';
500
+ }
501
+ });
502
+
503
+ function displayResult(result) {
504
+ resultContent.innerHTML = `
505
+ <div class="result-card slide-up">
506
+ <div class="result-highlight">
507
+ <div class="result-title">Cluster ID: ${result.cluster_id}</div>
508
+ <div class="result-content"><strong>Name:</strong> ${result.cluster_name}</div>
509
+ </div>
510
+
511
+ <div class="result-highlight">
512
+ <div class="result-title">Description</div>
513
+ <div class="result-content">${result.description}</div>
514
+ </div>
515
+
516
+ <div class="result-highlight">
517
+ <div class="result-title">Recommendation</div>
518
+ <div class="result-content">${result.recommendation}</div>
519
+ </div>
520
+ </div>
521
+ `;
522
+ }
523
+
524
+ // Add input validation
525
+ const inputs = form.querySelectorAll('input');
526
+ inputs.forEach(input => {
527
+ input.addEventListener('input', function() {
528
+ const min = parseInt(this.min);
529
+ const max = parseInt(this.max);
530
+ const value = parseInt(this.value);
531
+
532
+ if (value < min || value > max) {
533
+ this.style.borderColor = 'var(--error)';
534
+ } else {
535
+ this.style.borderColor = '#404040';
536
+ }
537
+ });
538
+ });
539
+
540
+ // Add animation to form elements on focus
541
+ inputs.forEach(input => {
542
+ input.addEventListener('focus', function() {
543
+ this.parentElement.style.transform = 'translateY(-2px)';
544
+ });
545
+
546
+ input.addEventListener('blur', function() {
547
+ this.parentElement.style.transform = 'translateY(0)';
548
+ });
549
+ });
550
+
551
+ // Add smooth scrolling for mobile
552
+ if (window.innerWidth <= 768) {
553
+ form.addEventListener('submit', function() {
554
+ document.querySelector('.result-section').scrollIntoView({
555
+ behavior: 'smooth'
556
+ });
557
+ });
558
+ }
559
+ });
560
+ </script>
561
+ </body>
562
+ </html>
563
+
predict_helper.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import joblib
2
+ import pandas as pd
3
+
4
+
5
+ kmeans = joblib.load('artifacts/kmeans.pkl')
6
+ scaler = joblib.load('artifacts/scaler.pkl')
7
+
8
+ CLUSTER_INFO = {
9
+ 0: {
10
+ "name": "High-Value Loyal Shoppers",
11
+ "description": "High income, high total spending, prefers in-store shopping, moderately recent purchases.",
12
+ "recommendation": "Offer exclusive in-store experiences, VIP loyalty tiers, early access to new collections, and personalized concierge service."
13
+ },
14
+ 1: {
15
+ "name": "Budget-Conscious Occasional Shoppers",
16
+ "description": "Low income, low spending, high web browsing, but made a very recent purchase.",
17
+ "recommendation": "Target with limited-time discounts, entry-level product bundles, and personalized email offers based on browsing history to encourage repeat purchases."
18
+ },
19
+ 2: {
20
+ "name": "Mid-Tier Engaged Browsers",
21
+ "description": "Mid-range income, low spending despite frequent website visits; hasn’t purchased in a long time.",
22
+ "recommendation": "Re-engage with cart abandonment reminders, free shipping thresholds, or 'we miss you' incentives (e.g., 15% off). Highlight bestsellers and social proof to drive conversion."
23
+ },
24
+ 3: {
25
+ "name": "Active Online-Focused Shoppers",
26
+ "description": "High income, high spending, shops frequently both online and in-store—with strongest activity on web.",
27
+ "recommendation": "Offer premium bundles, omnichannel loyalty rewards (e.g., buy online, pick up in-store + bonus points), and personalized cross-channel recommendations."
28
+ }
29
+ }
30
+
31
+ def predict(age, income, total_spending, num_web_purchases, num_store_purchases, num_web_visits, recency):
32
+ input_data = pd.DataFrame({
33
+ "Age": [age],
34
+ "Income": [income],
35
+ "Total_Spendings": [total_spending],
36
+ "NumWebPurchases": [num_web_purchases],
37
+ "NumStorePurchases": [num_store_purchases],
38
+ "NumWebVisitsMonth": [num_web_visits],
39
+ "Recency": [recency]})
40
+
41
+ scaled_data = scaler.transform(input_data)
42
+ cluster_id = kmeans.predict(scaled_data)[0]
43
+
44
+ info = CLUSTER_INFO[cluster_id]
45
+ return {
46
+ "cluster_id": int(cluster_id),
47
+ "cluster_name": info["name"],
48
+ "description": info["description"],
49
+ "recommendation": info["recommendation"]
50
+ }
51
+
52
+
53
+
54
+ """if __name__ == "__main__":
55
+ result = predict(45, 60000, 900, 6, 8, 5, 40)
56
+ print(f"✨ Customer Segment: {result['cluster_name']} (ID: {result['cluster_id']})")
57
+ print(f"📝 Profile: {result['description']}")
58
+ print(f"🎯 Marketing Action: {result['recommendation']}")"""
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ openai
4
+ joblib
5
+ numpy
6
+ pandas
7
+ scikit-learn
8
+ matplotlib
9
+ seaborn
10
+ numpy
11
+ python-multipart