SamiKoen commited on
Commit
cba1186
·
verified ·
1 Parent(s): 67415a7

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +1094 -0
  2. product_search.py +284 -0
  3. whatsapp_improved_chatbot.py +298 -0
app.py ADDED
@@ -0,0 +1,1094 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import requests
4
+ import xml.etree.ElementTree as ET
5
+ import warnings
6
+ import time
7
+ import threading
8
+ from concurrent.futures import ThreadPoolExecutor, as_completed
9
+ from fastapi import FastAPI, Request
10
+ from twilio.rest import Client
11
+ from twilio.twiml.messaging_response import MessagingResponse
12
+
13
+ # Yeni modüller - Basit sistem
14
+ from prompts import get_prompt_content_only
15
+ from whatsapp_renderer import extract_product_info_whatsapp
16
+ from whatsapp_passive_profiler import (
17
+ analyze_user_message, get_user_profile_summary, get_personalized_recommendations
18
+ )
19
+
20
+ # Import improved WhatsApp search for BF space
21
+ try:
22
+ from whatsapp_improved_chatbot import WhatsAppImprovedChatbot
23
+ USE_IMPROVED_SEARCH = True
24
+ except ImportError:
25
+ print("Improved WhatsApp chatbot not available, using basic search")
26
+ USE_IMPROVED_SEARCH = False
27
+
28
+ # LOGGING EN BAŞA EKLENDİ
29
+ import logging
30
+ logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
31
+ logger = logging.getLogger(__name__)
32
+
33
+ warnings.simplefilter('ignore')
34
+
35
+ # API ayarları
36
+ API_URL = "https://api.openai.com/v1/chat/completions"
37
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
38
+ logger.info(f"OpenAI API Key var mı: {'Evet' if OPENAI_API_KEY else 'Hayır'}")
39
+
40
+ # Twilio WhatsApp ayarları
41
+ TWILIO_ACCOUNT_SID = os.getenv("TWILIO_ACCOUNT_SID")
42
+ TWILIO_AUTH_TOKEN = os.getenv("TWILIO_AUTH_TOKEN")
43
+ TWILIO_MESSAGING_SERVICE_SID = os.getenv("TWILIO_MESSAGING_SERVICE_SID")
44
+ TWILIO_WHATSAPP_NUMBER = "whatsapp:+905332047254"
45
+
46
+ logger.info(f"Twilio SID var mı: {'Evet' if TWILIO_ACCOUNT_SID else 'Hayır'}")
47
+ logger.info(f"Twilio Auth Token var mı: {'Evet' if TWILIO_AUTH_TOKEN else 'Hayır'}")
48
+ logger.info(f"Messaging Service SID var mı: {'Evet' if TWILIO_MESSAGING_SERVICE_SID else 'Hayır'}")
49
+
50
+ if not TWILIO_ACCOUNT_SID or not TWILIO_AUTH_TOKEN:
51
+ logger.error("❌ Twilio bilgileri eksik!")
52
+ twilio_client = None
53
+ else:
54
+ try:
55
+ twilio_client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
56
+ logger.info("✅ Twilio client başarıyla oluşturuldu!")
57
+ except Exception as e:
58
+ logger.error(f"❌ Twilio client hatası: {e}")
59
+ twilio_client = None
60
+
61
+ # Trek bisiklet ürünlerini çekme - DÜZELTİLMİŞ VERSİYON
62
+ try:
63
+ print("🔍 XML DEBUG TEST BAŞLADI")
64
+ print("========================")
65
+
66
+ url = 'https://www.trekbisiklet.com.tr/output/8582384479'
67
+ print(f"📡 URL: {url}")
68
+
69
+ print("📥 HTTP isteği gönderiliyor...")
70
+ response = requests.get(url, verify=False, timeout=10)
71
+ print(f"✅ HTTP Status: {response.status_code}")
72
+ print(f"📏 Content Length: {len(response.content)} bytes")
73
+
74
+ # İlk 500 karakteri göster
75
+ content_preview = response.content[:500].decode('utf-8', errors='ignore')
76
+ print(f"📄 İlk 500 karakter:")
77
+ print(content_preview)
78
+ print("...")
79
+
80
+ print("\n🔄 XML parsing...")
81
+ root = ET.fromstring(response.content)
82
+ print(f"✅ XML root tag: {root.tag}")
83
+
84
+ all_items = root.findall('item')
85
+ print(f"📦 Toplam item sayısı: {len(all_items)}")
86
+
87
+ # İlk birkaç item'ı incele
88
+ print(f"\n🔍 İlk 3 item analizi:")
89
+ for i, item in enumerate(all_items[:3]):
90
+ print(f"\n--- ITEM {i+1} ---")
91
+
92
+ rootlabel = item.find('rootlabel')
93
+ if rootlabel is not None and rootlabel.text:
94
+ print(f"📛 Ürün: {rootlabel.text}")
95
+ else:
96
+ print("❌ rootlabel bulunamadı")
97
+
98
+ stock = item.find('stockAmount')
99
+ if stock is not None and stock.text:
100
+ print(f"📦 Stok: {stock.text}")
101
+ else:
102
+ print("❌ stockAmount bulunamadı")
103
+
104
+ price = item.find('priceTaxWithCur')
105
+ if price is not None and price.text:
106
+ print(f"💰 Fiyat: {price.text}")
107
+ else:
108
+ print("❌ priceTaxWithCur bulunamadı")
109
+
110
+ # Marlin arama testi
111
+ print(f"\n🎯 MARLIN ARAMA TESİ:")
112
+ marlin_count = 0
113
+ products = []
114
+
115
+ for item in all_items:
116
+ # Değişkenleri önceden tanımla
117
+ stock_number = 0
118
+ stock_amount = "stokta değil"
119
+ price = ""
120
+ price_eft = ""
121
+ product_link = ""
122
+
123
+ rootlabel = item.find('rootlabel')
124
+ if rootlabel is None or not rootlabel.text:
125
+ continue
126
+
127
+ full_name = rootlabel.text.strip()
128
+ name_words = full_name.lower().split()
129
+ name = name_words[0] if name_words else "unknown"
130
+
131
+ # STOK KONTROLÜ - SAYISAL KARŞILAŞTIRMA
132
+ stock_element = item.find('stockAmount')
133
+ if stock_element is not None and stock_element.text:
134
+ try:
135
+ stock_number = int(stock_element.text.strip())
136
+ stock_amount = "stokta" if stock_number > 0 else "stokta değil"
137
+ except (ValueError, TypeError):
138
+ stock_number = 0
139
+ stock_amount = "stokta değil"
140
+
141
+ # Marlin kontrolü
142
+ if 'marlin' in full_name.lower():
143
+ marlin_count += 1
144
+ print(f"🎯 MARLIN BULUNDU #{marlin_count}: {full_name}")
145
+ print(f" 📦 Stok: {stock_number} adet")
146
+ print(f" 📊 Durum: {stock_amount}")
147
+
148
+ # Stokta olan ürünler için fiyat bilgilerini al
149
+ if stock_amount == "stokta":
150
+ # Normal fiyat
151
+ price_element = item.find('priceTaxWithCur')
152
+ price_str = price_element.text if price_element is not None and price_element.text else "0"
153
+
154
+ # Kampanya fiyatı
155
+ price_rebate_element = item.find('priceRebateWithTax')
156
+ price_rebate_str = price_rebate_element.text if price_rebate_element is not None and price_rebate_element.text else ""
157
+
158
+ # Kampanya fiyatı varsa onu kullan, yoksa normal fiyatı kullan
159
+ final_price_str = price_str
160
+ if price_rebate_str:
161
+ try:
162
+ normal_price = float(price_str)
163
+ rebate_price = float(price_rebate_str)
164
+ # Kampanya fiyatı normal fiyattan farklı ve düşükse kullan
165
+ if rebate_price < normal_price:
166
+ final_price_str = price_rebate_str
167
+ except (ValueError, TypeError):
168
+ final_price_str = price_str
169
+
170
+ # EFT fiyatı
171
+ price_eft_element = item.find('priceEft')
172
+ price_eft_str = price_eft_element.text if price_eft_element is not None and price_eft_element.text else ""
173
+
174
+ # Ürün linki
175
+ link_element = item.find('productLink')
176
+ product_link = link_element.text if link_element is not None and link_element.text else ""
177
+
178
+ # Fiyat formatting (kampanya fiyatı veya normal fiyat)
179
+ try:
180
+ price_float = float(final_price_str)
181
+ if price_float > 200000:
182
+ price = str(round(price_float / 5000) * 5000)
183
+ elif price_float > 30000:
184
+ price = str(round(price_float / 1000) * 1000)
185
+ elif price_float > 10000:
186
+ price = str(round(price_float / 100) * 100)
187
+ else:
188
+ price = str(round(price_float / 10) * 10)
189
+ except (ValueError, TypeError):
190
+ price = final_price_str
191
+
192
+ # EFT fiyat formatting
193
+ if price_eft_str:
194
+ try:
195
+ price_eft_float = float(price_eft_str)
196
+ if price_eft_float > 200000:
197
+ price_eft = str(round(price_eft_float / 5000) * 5000)
198
+ elif price_eft_float > 30000:
199
+ price_eft = str(round(price_eft_float / 1000) * 1000)
200
+ elif price_eft_float > 10000:
201
+ price_eft = str(round(price_eft_float / 100) * 100)
202
+ else:
203
+ price_eft = str(round(price_eft_float / 10) * 10)
204
+ except (ValueError, TypeError):
205
+ price_eft = price_eft_str
206
+ else:
207
+ try:
208
+ price_eft_float = float(price_str)
209
+ price_eft = str(round(price_eft_float * 0.975 / 10) * 10)
210
+ except:
211
+ price_eft = ""
212
+
213
+ # Ürün bilgilerini tuple olarak oluştur
214
+ item_info = (stock_amount, price, product_link, price_eft, str(stock_number))
215
+ products.append((name, item_info, full_name))
216
+
217
+ print(f"\n📊 SONUÇ:")
218
+ print(f" Toplam ürün: {len(all_items)}")
219
+ print(f" Marlin ürünü: {marlin_count}")
220
+ print(f" Products listesi: {len(products)} ürün")
221
+
222
+ # Initialize improved WhatsApp chatbot for BF space
223
+ global improved_whatsapp_bot
224
+ improved_whatsapp_bot = None
225
+ if USE_IMPROVED_SEARCH:
226
+ try:
227
+ improved_whatsapp_bot = WhatsAppImprovedChatbot(products)
228
+ print("✅ BF Space: Improved WhatsApp product search initialized successfully")
229
+ except Exception as e:
230
+ print(f"❌ BF Space: Failed to initialize improved WhatsApp search: {e}")
231
+ USE_IMPROVED_SEARCH = False
232
+ improved_whatsapp_bot = None
233
+
234
+ # Enhanced features kaldırıldı - GPT-4 doğal dil anlama kullanacak
235
+ print("✅ Basit sistem aktif - GPT-4 doğal dil anlama")
236
+
237
+ if marlin_count == 0:
238
+ print("❌ XML'de hiç Marlin ürünü bulunamadı!")
239
+ print(" Muhtemelen XML'de sadece aksesuar/yedek parça var.")
240
+ else:
241
+ print("✅ Marlin ürünleri XML'de mevcut")
242
+
243
+ # Marlin stok raporu
244
+ marlin_products = [p for p in products if 'marlin' in p[2].lower()]
245
+ marlin_in_stock = [p for p in marlin_products if p[1][0] == "stokta"]
246
+ marlin_out_of_stock = [p for p in marlin_products if p[1][0] == "stokta değil"]
247
+
248
+ print(f"\n📊 MARLIN STOK RAPORU:")
249
+ print(f" 📦 Toplam Marlin modeli: {len(marlin_products)}")
250
+ print(f" ✅ Stokta olan: {len(marlin_in_stock)}")
251
+ print(f" ❌ Stokta olmayan: {len(marlin_out_of_stock)}")
252
+
253
+ if marlin_in_stock:
254
+ print("\n✅ STOKTA OLAN MARLIN MODELLERİ:")
255
+ for product in marlin_in_stock:
256
+ print(f" • {product[2]} - {product[1][1]} TL ({product[1][4]} adet)")
257
+
258
+ if marlin_out_of_stock:
259
+ print("\n❌ STOKTA OLMAYAN MARLIN MODELLERİ:")
260
+ for product in marlin_out_of_stock:
261
+ print(f" • {product[2]}")
262
+
263
+ except Exception as e:
264
+ print(f"❌ Ürün yükleme hatası: {e}")
265
+ import traceback
266
+ traceback.print_exc()
267
+ products = []
268
+
269
+ # ===============================
270
+ # STOK API ENTEGRASYONU
271
+ # ===============================
272
+
273
+ STOCK_API_BASE = "https://video.trek-turkey.com/bizimhesap-proxy.php"
274
+
275
+ # Stock cache (5 dakikalık cache)
276
+ stock_cache = {}
277
+ CACHE_DURATION = 300 # 5 dakika (saniye cinsinden)
278
+
279
+ def normalize_turkish(text):
280
+ """Türkçe karakterleri normalize et"""
281
+ if not text:
282
+ return ""
283
+ replacements = {
284
+ 'ı': 'i', 'İ': 'i', 'ş': 's', 'Ş': 's',
285
+ 'ğ': 'g', 'Ğ': 'g', 'ü': 'u', 'Ü': 'u',
286
+ 'ö': 'o', 'Ö': 'o', 'ç': 'c', 'Ç': 'c'
287
+ }
288
+ text = text.lower()
289
+ for tr_char, eng_char in replacements.items():
290
+ text = text.replace(tr_char, eng_char)
291
+ return text
292
+
293
+ def fetch_warehouse_inventory(warehouse, product_name, search_terms):
294
+ """Tek bir mağazanın stok bilgisini al"""
295
+ try:
296
+ warehouse_id = warehouse['id']
297
+ warehouse_name = warehouse['title']
298
+
299
+ # DSW'yi ayrı tut (gelecek stok için)
300
+ is_dsw = 'DSW' in warehouse_name or 'ÖN SİPARİŞ' in warehouse_name.upper()
301
+
302
+ # Mağaza stoklarını al
303
+ inventory_url = f"{STOCK_API_BASE}?action=inventory&warehouse={warehouse_id}&endpoint=inventory/{warehouse_id}"
304
+ inventory_response = requests.get(inventory_url, timeout=3, verify=False)
305
+
306
+ if inventory_response.status_code != 200:
307
+ return None
308
+
309
+ inventory_data = inventory_response.json()
310
+
311
+ # API yanıtını kontrol et
312
+ if 'data' not in inventory_data or 'inventory' not in inventory_data['data']:
313
+ return None
314
+
315
+ products = inventory_data['data']['inventory']
316
+
317
+ # Beden terimleri kontrolü
318
+ size_terms = ['xs', 's', 'm', 'ml', 'l', 'xl', 'xxl', '2xl', '3xl', 'small', 'medium', 'large']
319
+ size_numbers = ['44', '46', '48', '50', '52', '54', '56', '58', '60']
320
+
321
+ # Arama terimlerinde beden var mı kontrol et
322
+ has_size_query = False
323
+ size_query = None
324
+ for term in search_terms:
325
+ if term in size_terms or term in size_numbers:
326
+ has_size_query = True
327
+ size_query = term
328
+ break
329
+
330
+ # Eğer sadece beden sorgusu varsa (ör: "m", "xl")
331
+ is_only_size_query = len(search_terms) == 1 and has_size_query
332
+
333
+ # Ürünü ara
334
+ warehouse_variants = []
335
+ dsw_stock_count = 0
336
+
337
+ for product in products:
338
+ product_title = normalize_turkish(product.get('title', '')).lower()
339
+ original_title = product.get('title', '')
340
+
341
+ # Eğer sadece beden sorgusu ise
342
+ if is_only_size_query:
343
+ # Beden terimini ürün başlığında ara (parantez içinde veya dışında)
344
+ if size_query in product_title.split() or f'({size_query})' in product_title or f' {size_query} ' in product_title or product_title.endswith(f' {size_query}'):
345
+ qty = int(product.get('qty', 0))
346
+ stock = int(product.get('stock', 0))
347
+ actual_stock = max(qty, stock)
348
+
349
+ if actual_stock > 0:
350
+ if is_dsw:
351
+ dsw_stock_count += actual_stock
352
+ continue
353
+ warehouse_variants.append(f"{original_title}: ✓ Stokta")
354
+ else:
355
+ # Normal ürün araması - tüm terimler eşleşmeli
356
+ # Ama beden terimi varsa özel kontrol yap
357
+ if has_size_query:
358
+ # Beden hariç diğer terimleri kontrol et
359
+ non_size_terms = [t for t in search_terms if t != size_query]
360
+ product_matches = all(term in product_title for term in non_size_terms)
361
+
362
+ # Beden kontrolü - daha esnek
363
+ size_matches = size_query in product_title.split() or f'({size_query})' in product_title or f' {size_query} ' in product_title or product_title.endswith(f' {size_query}')
364
+
365
+ if product_matches and size_matches:
366
+ qty = int(product.get('qty', 0))
367
+ stock = int(product.get('stock', 0))
368
+ actual_stock = max(qty, stock)
369
+
370
+ if actual_stock > 0:
371
+ if is_dsw:
372
+ dsw_stock_count += actual_stock
373
+ continue
374
+
375
+ # Varyant bilgisini göster
376
+ variant_info = original_title
377
+ possible_names = [
378
+ product_name.upper(),
379
+ product_name.lower(),
380
+ product_name.title(),
381
+ product_name.upper().replace('I', 'İ'),
382
+ product_name.upper().replace('İ', 'I'),
383
+ ]
384
+
385
+ if 'fx sport' in product_name.lower():
386
+ possible_names.extend(['FX Sport AL 3', 'FX SPORT AL 3', 'Fx Sport Al 3'])
387
+
388
+ for possible_name in possible_names:
389
+ variant_info = variant_info.replace(possible_name, '').strip()
390
+
391
+ variant_info = ' '.join(variant_info.split())
392
+
393
+ if variant_info and variant_info != original_title:
394
+ warehouse_variants.append(f"{variant_info}: ✓ Stokta")
395
+ else:
396
+ warehouse_variants.append(f"{original_title}: ✓ Stokta")
397
+ else:
398
+ # Beden sorgusu yoksa normal kontrol
399
+ if all(term in product_title for term in search_terms):
400
+ qty = int(product.get('qty', 0))
401
+ stock = int(product.get('stock', 0))
402
+ actual_stock = max(qty, stock)
403
+
404
+ if actual_stock > 0:
405
+ if is_dsw:
406
+ dsw_stock_count += actual_stock
407
+ continue
408
+
409
+ variant_info = original_title
410
+ possible_names = [
411
+ product_name.upper(),
412
+ product_name.lower(),
413
+ product_name.title(),
414
+ product_name.upper().replace('I', 'İ'),
415
+ product_name.upper().replace('İ', 'I'),
416
+ ]
417
+
418
+ if 'fx sport' in product_name.lower():
419
+ possible_names.extend(['FX Sport AL 3', 'FX SPORT AL 3', 'Fx Sport Al 3'])
420
+
421
+ for possible_name in possible_names:
422
+ variant_info = variant_info.replace(possible_name, '').strip()
423
+
424
+ variant_info = ' '.join(variant_info.split())
425
+
426
+ if variant_info and variant_info != original_title:
427
+ warehouse_variants.append(f"{variant_info}: ✓ Stokta")
428
+ else:
429
+ warehouse_variants.append(f"{original_title}: ✓ Stokta")
430
+
431
+ # Sonuç döndür
432
+ if warehouse_variants and not is_dsw:
433
+ return {'warehouse': warehouse_name, 'variants': warehouse_variants, 'is_dsw': False}
434
+ elif dsw_stock_count > 0:
435
+ return {'dsw_stock': dsw_stock_count, 'is_dsw': True}
436
+
437
+ return None
438
+
439
+ except Exception:
440
+ return None
441
+
442
+ def get_realtime_stock_parallel(product_name):
443
+ """API'den gerçek zamanlı stok bilgisini çek - Paralel versiyon with cache"""
444
+ try:
445
+ # Cache kontrolü
446
+ cache_key = normalize_turkish(product_name).lower()
447
+ current_time = time.time()
448
+
449
+ if cache_key in stock_cache:
450
+ cached_data, cached_time = stock_cache[cache_key]
451
+ # Cache hala geçerli mi?
452
+ if current_time - cached_time < CACHE_DURATION:
453
+ logger.info(f"Cache'den döndürülüyor: {product_name}")
454
+ return cached_data
455
+
456
+ # Önce mağaza listesini al
457
+ warehouses_url = f"{STOCK_API_BASE}?action=warehouses&endpoint=warehouses"
458
+ warehouses_response = requests.get(warehouses_url, timeout=3, verify=False)
459
+
460
+ if warehouses_response.status_code != 200:
461
+ logger.error(f"Mağaza listesi alınamadı: {warehouses_response.status_code}")
462
+ return None
463
+
464
+ warehouses_data = warehouses_response.json()
465
+
466
+ # API yanıtını kontrol et
467
+ if 'data' not in warehouses_data or 'warehouses' not in warehouses_data['data']:
468
+ logger.error("Mağaza verisi bulunamadı")
469
+ return None
470
+
471
+ warehouses = warehouses_data['data']['warehouses']
472
+
473
+ # Ürün adını normalize et
474
+ search_terms = normalize_turkish(product_name).lower().split()
475
+ logger.info(f"Aranan ürün: {product_name} -> {search_terms}")
476
+
477
+ stock_info = {}
478
+ total_dsw_stock = 0
479
+ total_stock = 0
480
+
481
+ # Paralel olarak tüm mağazaları sorgula
482
+ with ThreadPoolExecutor(max_workers=10) as executor:
483
+ # Tüm mağazalar için görev oluştur
484
+ futures = {
485
+ executor.submit(fetch_warehouse_inventory, warehouse, product_name, search_terms): warehouse
486
+ for warehouse in warehouses
487
+ }
488
+
489
+ # Sonuçları topla
490
+ for future in as_completed(futures):
491
+ result = future.result()
492
+ if result:
493
+ if result.get('is_dsw'):
494
+ total_dsw_stock += result.get('dsw_stock', 0)
495
+ else:
496
+ warehouse_name = result['warehouse']
497
+ stock_info[warehouse_name] = result['variants']
498
+ total_stock += 1 # En az bir mağazada var
499
+
500
+ # Sonucu oluştur
501
+ if not stock_info:
502
+ # Mağazada yok ama DSW'de varsa
503
+ if total_dsw_stock > 0:
504
+ result = f"{product_name}: Şu anda mağazalarda stokta yok, ancak yakında gelecek. Ön sipariş verebilirsiniz."
505
+ else:
506
+ result = f"{product_name}: Şu anda hiçbir mağazada stokta bulunmuyor."
507
+ else:
508
+ # Minimal prompt oluştur - varyant detaylarıyla
509
+ prompt_lines = [f"{product_name} stok durumu:"]
510
+ for warehouse, variants in stock_info.items():
511
+ if isinstance(variants, list):
512
+ prompt_lines.append(f"- {warehouse}:")
513
+ for variant in variants:
514
+ prompt_lines.append(f" • {variant}")
515
+ else:
516
+ prompt_lines.append(f"- {warehouse}: {variants}")
517
+
518
+ # Güvenlik: Toplam adet bilgisi gösterme
519
+ if total_stock > 0:
520
+ prompt_lines.append(f"✓ Ürün stokta mevcut")
521
+
522
+ result = "\n".join(prompt_lines)
523
+
524
+ # Sonucu cache'e kaydet
525
+ stock_cache[cache_key] = (result, current_time)
526
+
527
+ return result
528
+
529
+ except Exception as e:
530
+ logger.error(f"API hatası: {e}")
531
+ return None
532
+
533
+ def is_stock_query(message):
534
+ """Mesajın stok sorgusu olup olmadığını kontrol et"""
535
+ stock_keywords = ['stok', 'stock', 'var mı', 'mevcut mu', 'kaç adet',
536
+ 'kaç tane', 'bulunuyor mu', 'hangi mağaza',
537
+ 'nerede var', 'beden', 'numara']
538
+ message_lower = message.lower()
539
+ return any(keyword in message_lower for keyword in stock_keywords)
540
+
541
+ # Sistem mesajları - Modüler prompts'tan yükle
542
+ def get_system_messages():
543
+ return get_prompt_content_only() # prompts.py'dan yükle
544
+
545
+ # ===============================
546
+ # SOHBET HAFIZASI SİSTEMİ
547
+ # ===============================
548
+
549
+ # Sohbet hafızası için basit bir dictionary
550
+ conversation_memory = {}
551
+
552
+ def get_conversation_context(phone_number):
553
+ """Kullanıcının sohbet geçmişini getir"""
554
+ if phone_number not in conversation_memory:
555
+ conversation_memory[phone_number] = {
556
+ "messages": [],
557
+ "current_category": None,
558
+ "last_activity": None
559
+ }
560
+ return conversation_memory[phone_number]
561
+
562
+ def add_to_conversation(phone_number, user_message, ai_response):
563
+ """Sohbet geçmişine ekle"""
564
+ import datetime
565
+
566
+ context = get_conversation_context(phone_number)
567
+ context["last_activity"] = datetime.datetime.now()
568
+
569
+ context["messages"].append({
570
+ "user": user_message,
571
+ "ai": ai_response,
572
+ "timestamp": datetime.datetime.now()
573
+ })
574
+
575
+ # Sadece son 10 mesajı tut
576
+ if len(context["messages"]) > 10:
577
+ context["messages"] = context["messages"][-10:]
578
+
579
+ detect_category(phone_number, user_message, ai_response)
580
+
581
+ def detect_category(phone_number, user_message, ai_response):
582
+ """Konuşulan kategoriyi tespit et"""
583
+ context = get_conversation_context(phone_number)
584
+
585
+ categories = {
586
+ "marlin": ["marlin", "marlin+"],
587
+ "madone": ["madone"],
588
+ "emonda": ["emonda", "émonda"],
589
+ "domane": ["domane"],
590
+ "checkpoint": ["checkpoint"],
591
+ "fuel": ["fuel", "fuel ex", "fuel exe"],
592
+ "procaliber": ["procaliber"],
593
+ "supercaliber": ["supercaliber"],
594
+ "fx": ["fx"],
595
+ "ds": ["ds", "dual sport"],
596
+ "powerfly": ["powerfly"],
597
+ "rail": ["rail"],
598
+ "verve": ["verve"],
599
+ "townie": ["townie"]
600
+ }
601
+
602
+ user_lower = user_message.lower()
603
+ for category, keywords in categories.items():
604
+ for keyword in keywords:
605
+ if keyword in user_lower:
606
+ context["current_category"] = category
607
+ print(f"🎯 Kategori tespit edildi: {category} ({phone_number})")
608
+ return category
609
+
610
+ return context.get("current_category")
611
+
612
+ def build_context_messages(phone_number, current_message):
613
+ """Sohbet geçmişi ile sistem mesajlarını oluştur"""
614
+ context = get_conversation_context(phone_number)
615
+ system_messages = get_system_messages()
616
+
617
+ # Mevcut kategori varsa, sistem mesajına ekle
618
+ if context.get("current_category"):
619
+ category_msg = f"Kullanıcı şu anda {context['current_category'].upper()} kategorisi hakkında konuşuyor. Tüm cevaplarını bu kategori bağlamında ver. Kullanıcı yeni bir kategori belirtmediği sürece {context['current_category']} hakkında bilgi vermek istediğini varsay."
620
+ system_messages.append({"role": "system", "content": category_msg})
621
+
622
+ # Son 3 mesaj alışverişini ekle
623
+ recent_messages = context["messages"][-3:] if context["messages"] else []
624
+
625
+ all_messages = system_messages.copy()
626
+
627
+ # Geçmiş mesajları ekle
628
+ for msg in recent_messages:
629
+ all_messages.append({"role": "user", "content": msg["user"]})
630
+ all_messages.append({"role": "assistant", "content": msg["ai"]})
631
+
632
+ # Mevcut mesajı ekle
633
+ all_messages.append({"role": "user", "content": current_message})
634
+
635
+ return all_messages
636
+
637
+ def process_whatsapp_message_with_memory(user_message, phone_number):
638
+ """Hafızalı WhatsApp mesaj işleme"""
639
+ try:
640
+ # 🧠 Pasif profil analizi - kullanıcı mesajını analiz et
641
+ profile_analysis = analyze_user_message(phone_number, user_message)
642
+ logger.info(f"📊 Profil analizi: {phone_number} -> {profile_analysis}")
643
+
644
+ # 🎯 Kişiselleştirilmiş öneriler kontrolü
645
+ if any(keyword in user_message.lower() for keyword in ["öneri", "öner", "tavsiye", "ne önerirsin"]):
646
+ personalized = get_personalized_recommendations(phone_number, products)
647
+ if personalized.get("personalized") and personalized.get("recommendations"):
648
+ # Kullanıcı profiline göre özelleştirilmiş cevap hazırla
649
+ profile_summary = get_user_profile_summary(phone_number)
650
+ custom_response = create_personalized_response(personalized, profile_summary)
651
+ return extract_product_info_whatsapp(custom_response)
652
+
653
+ # Enhanced features kaldırıldı - GPT-4 doğal dil anlama kullanacak
654
+
655
+ # STOK SORGUSU KONTROLÜ
656
+ if is_stock_query(user_message):
657
+ logger.info("Stok sorgusu algılandı, API'den veri çekiliyor...")
658
+
659
+ # Mesajdan ürün adını çıkarmaya çalış
660
+ product_words = []
661
+ skip_words = ['stok', 'stock', 'kaç', 'adet', 'tane', 'var', 'mı', 'mi',
662
+ 'mevcut', 'mu', 'bulunuyor', 'hangi', 'mağaza', 'nerede',
663
+ 'durumu', 'stoklarda', 'stokta', 'için', 've', 'ile', 'toplam', 'toplamda',
664
+ 'fiyat', 'fiyatı', 'ne', 'nedir', 'kadar', 'beden', 'bedeni', 'bedenli']
665
+
666
+ # Beden terimleri - bunları skip etmemeliyiz
667
+ size_terms = ['xs', 's', 'm', 'ml', 'l', 'xl', 'xxl', '2xl', '3xl', 'small', 'medium', 'large',
668
+ '44', '46', '48', '50', '52', '54', '56', '58', '60']
669
+
670
+ for word in user_message.lower().split():
671
+ if word not in skip_words:
672
+ product_words.append(word)
673
+
674
+ if product_words:
675
+ product_name = ' '.join(product_words)
676
+
677
+ # Sadece beden sorgusu mu kontrol et
678
+ is_only_size = len(product_words) == 1 and product_words[0].lower() in size_terms
679
+
680
+ if is_only_size:
681
+ product_name = product_words[0].lower()
682
+ logger.info(f"Sadece beden sorgusu: {product_name}")
683
+
684
+ # API'den stok bilgisi al
685
+ stock_info = get_realtime_stock_parallel(product_name)
686
+ if stock_info:
687
+ # WhatsApp için formatla
688
+ formatted_response = extract_product_info_whatsapp(stock_info)
689
+
690
+ # Sohbet geçmişine ekle
691
+ add_to_conversation(phone_number, user_message, formatted_response)
692
+
693
+ return formatted_response
694
+
695
+ # Sohbet geçmişi ile sistem mesajlarını oluştur
696
+ messages = build_context_messages(phone_number, user_message)
697
+
698
+ # 🎯 Profil bilgilerini sistem mesajlarına ekle
699
+ profile_summary = get_user_profile_summary(phone_number)
700
+ if profile_summary.get("exists") and profile_summary.get("confidence", 0) > 0.3:
701
+ profile_context = create_profile_context_message(profile_summary)
702
+ messages.append({"role": "system", "content": profile_context})
703
+
704
+ # 🔍 BF Space: Use improved product search if available
705
+ product_found_improved = False
706
+ if USE_IMPROVED_SEARCH and improved_whatsapp_bot:
707
+ try:
708
+ product_result = improved_whatsapp_bot.process_message(user_message)
709
+ if product_result['is_product_query'] and product_result['response']:
710
+ # Use improved search response directly
711
+ messages.append({
712
+ "role": "system",
713
+ "content": f"ÜRÜN BİLGİSİ (BF Space):\n{product_result['response']}\n\nSADECE bu bilgileri kullanarak kullanıcıya yardımcı ol. Bu bilgiler dışında ek bilgi ekleme."
714
+ })
715
+ product_found_improved = True
716
+ logger.info("✅ BF Space: Improved product search used")
717
+ except Exception as e:
718
+ logger.error(f"❌ BF Space: Improved search error: {e}")
719
+
720
+ # Fallback to basic search if improved search didn't work
721
+ if not product_found_improved:
722
+ # Ürün bilgilerini kontrol et ve ekle (basic search)
723
+ input_words = user_message.lower().split()
724
+ for product_info in products:
725
+ if product_info[0] in input_words:
726
+ if product_info[1][0] == "stokta":
727
+ normal_price = f"Fiyat: {product_info[1][1]} TL"
728
+ if product_info[1][3]:
729
+ eft_price = f"Havale: {product_info[1][3]} TL"
730
+ price_info = f"{normal_price}, {eft_price}"
731
+ else:
732
+ price_info = normal_price
733
+
734
+ new_msg = f"{product_info[2]} {product_info[1][0]} - {price_info}"
735
+ else:
736
+ new_msg = f"{product_info[2]} {product_info[1][0]}"
737
+ messages.append({"role": "system", "content": new_msg})
738
+ break # Avoid duplicates
739
+
740
+ if not OPENAI_API_KEY:
741
+ return "OpenAI API anahtarı eksik. Lütfen environment variables'ları kontrol edin."
742
+
743
+ payload = {
744
+ "model": "gpt-5-chat-latest",
745
+ "messages": messages,
746
+ "temperature": 0,
747
+ "max_tokens": 800,
748
+ "stream": False,
749
+ }
750
+
751
+ headers = {
752
+ "Content-Type": "application/json",
753
+ "Authorization": f"Bearer {OPENAI_API_KEY}"
754
+ }
755
+
756
+ response = requests.post(API_URL, headers=headers, json=payload)
757
+ if response.status_code == 200:
758
+ result = response.json()
759
+ ai_response = result['choices'][0]['message']['content']
760
+
761
+ # WhatsApp için resim URL'lerini formatla
762
+ formatted_response = extract_product_info_whatsapp(ai_response)
763
+
764
+ # Sohbet geçmişine ekle
765
+ add_to_conversation(phone_number, user_message, formatted_response)
766
+
767
+ return formatted_response
768
+ else:
769
+ print(f"OpenAI API Error: {response.status_code} - {response.text}")
770
+ return f"API hatası: {response.status_code}. Lütfen daha sonra tekrar deneyin."
771
+
772
+ except Exception as e:
773
+ print(f"❌ WhatsApp mesaj işleme hatası: {e}")
774
+ return "Teknik bir sorun oluştu. Lütfen daha sonra tekrar deneyin."
775
+
776
+ def create_profile_context_message(profile_summary):
777
+ """Profil bilgilerini sistem mesajına çevir"""
778
+ context_parts = []
779
+
780
+ preferences = profile_summary.get("preferences", {})
781
+ behavior = profile_summary.get("behavior", {})
782
+
783
+ # Bütçe bilgisi
784
+ if preferences.get("budget_min") and preferences.get("budget_max"):
785
+ budget_min = preferences["budget_min"]
786
+ budget_max = preferences["budget_max"]
787
+ context_parts.append(f"Kullanıcının bütçesi: {budget_min:,}-{budget_max:,} TL")
788
+
789
+ # Kategori tercihleri
790
+ if preferences.get("categories"):
791
+ categories = ", ".join(preferences["categories"])
792
+ context_parts.append(f"İlgilendiği kategoriler: {categories}")
793
+
794
+ # Kullanım amacı
795
+ if preferences.get("usage_purpose"):
796
+ purposes = ", ".join(preferences["usage_purpose"])
797
+ context_parts.append(f"Kullanım amacı: {purposes}")
798
+
799
+ # Davranış kalıpları
800
+ if behavior.get("price_sensitive"):
801
+ context_parts.append("Fiyata duyarlı bir kullanıcı")
802
+ if behavior.get("tech_interested"):
803
+ context_parts.append("Teknik detaylarla ilgilenen bir kullanıcı")
804
+ if behavior.get("comparison_lover"):
805
+ context_parts.append("Karşılaştırma yapmayı seven bir kullanıcı")
806
+
807
+ # Etkileşim stili
808
+ interaction_style = profile_summary.get("interaction_style", "balanced")
809
+ style_descriptions = {
810
+ "analytical": "Detaylı ve analitik bilgi bekleyen",
811
+ "budget_conscious": "Bütçe odaklı ve ekonomik seçenekleri arayan",
812
+ "technical": "Teknik özellikler ve spesifikasyonlarla ilgilenen",
813
+ "decisive": "Hızlı karar veren ve özet bilgi isteyen",
814
+ "balanced": "Dengeli yaklaşım sergileyen"
815
+ }
816
+ context_parts.append(f"{style_descriptions.get(interaction_style, 'balanced')} bir kullanıcı")
817
+
818
+ if context_parts:
819
+ return f"Kullanıcı profili: {'. '.join(context_parts)}. Bu bilgileri göz önünde bulundurarak cevap ver."
820
+ return ""
821
+
822
+ def create_personalized_response(personalized_data, profile_summary):
823
+ """Kişiselleştirilmiş öneri cevabı oluştur"""
824
+ response_parts = []
825
+
826
+ # Kullanıcı stiline göre selamlama
827
+ interaction_style = profile_summary.get("interaction_style", "balanced")
828
+ if interaction_style == "analytical":
829
+ response_parts.append("🔍 Profilinizi analiz ederek sizin için en uygun seçenekleri belirledim:")
830
+ elif interaction_style == "budget_conscious":
831
+ response_parts.append("💰 Bütçenize uygun en iyi seçenekleri hazırladım:")
832
+ elif interaction_style == "technical":
833
+ response_parts.append("⚙️ Teknik tercihlerinize göre önerilerim:")
834
+ else:
835
+ response_parts.append("🎯 Size özel seçtiklerim:")
836
+
837
+ # Önerileri listele
838
+ recommendations = personalized_data.get("recommendations", [])[:3] # İlk 3 öneri
839
+
840
+ if recommendations:
841
+ response_parts.append("\n")
842
+ for i, product in enumerate(recommendations, 1):
843
+ name, item_info, full_name = product
844
+ price = item_info[1] if len(item_info) > 1 else "Fiyat yok"
845
+ response_parts.append(f"**{i}. {full_name}**")
846
+ response_parts.append(f"💰 Fiyat: {price} TL")
847
+ response_parts.append("")
848
+
849
+ # Profil bazlı açıklama
850
+ preferences = profile_summary.get("preferences", {})
851
+ if preferences.get("categories"):
852
+ category = preferences["categories"][0]
853
+ response_parts.append(f"Bu öneriler {category} kategorisindeki ilginizi ve tercihlerinizi dikkate alarak hazırlandı.")
854
+
855
+ return "\n".join(response_parts)
856
+
857
+ def split_long_message(message, max_length=1600):
858
+ """Uzun mesajları WhatsApp için uygun parçalara böler"""
859
+ if len(message) <= max_length:
860
+ return [message]
861
+
862
+ parts = []
863
+ remaining = message
864
+
865
+ while len(remaining) > max_length:
866
+ cut_point = max_length
867
+
868
+ # Geriye doğru git ve uygun kesme noktası ara
869
+ for i in range(max_length, max_length - 200, -1):
870
+ if i < len(remaining) and remaining[i] in ['.', '!', '?', '\n']:
871
+ cut_point = i + 1
872
+ break
873
+ elif i < len(remaining) and remaining[i] in [' ', ',', ';']:
874
+ cut_point = i
875
+
876
+ parts.append(remaining[:cut_point].strip())
877
+ remaining = remaining[cut_point:].strip()
878
+
879
+ if remaining:
880
+ parts.append(remaining)
881
+
882
+ return parts
883
+
884
+ # ===============================
885
+ # HAFIZA SİSTEMİ SONU
886
+ # ===============================
887
+
888
+ # WhatsApp mesajı işleme (eski fonksiyon - yedek için)
889
+ def process_whatsapp_message(user_message):
890
+ try:
891
+ system_messages = get_system_messages()
892
+
893
+ # 🔍 BF Space: Use improved product search if available (backup function)
894
+ product_found_improved = False
895
+ if USE_IMPROVED_SEARCH and improved_whatsapp_bot:
896
+ try:
897
+ product_result = improved_whatsapp_bot.process_message(user_message)
898
+ if product_result['is_product_query'] and product_result['response']:
899
+ system_messages.append({
900
+ "role": "system",
901
+ "content": f"ÜRÜN BİLGİSİ (BF Space Backup):\n{product_result['response']}\n\nSADECE bu bilgileri kullanarak kullanıcıya yardımcı ol. Bu bilgiler dışında ek bilgi ekleme."
902
+ })
903
+ product_found_improved = True
904
+ except Exception as e:
905
+ print(f"BF Space backup: Improved search error: {e}")
906
+
907
+ # Fallback to basic search
908
+ if not product_found_improved:
909
+ # Ürün bilgilerini kontrol et (basic search)
910
+ input_words = user_message.lower().split()
911
+ for product_info in products:
912
+ if product_info[0] in input_words:
913
+ if product_info[1][0] == "stokta":
914
+ normal_price = f"Fiyat: {product_info[1][1]} TL"
915
+ if product_info[1][3]:
916
+ eft_price = f"Havale: {product_info[1][3]} TL"
917
+ price_info = f"{normal_price}, {eft_price}"
918
+ else:
919
+ price_info = normal_price
920
+
921
+ new_msg = f"{product_info[2]} {product_info[1][0]} - {price_info}"
922
+ else:
923
+ new_msg = f"{product_info[2]} {product_info[1][0]}"
924
+ system_messages.append({"role": "system", "content": new_msg})
925
+ break
926
+
927
+ messages = system_messages + [{"role": "user", "content": user_message}]
928
+
929
+ if not OPENAI_API_KEY:
930
+ return "OpenAI API anahtarı eksik. Lütfen environment variables'ları kontrol edin."
931
+
932
+ payload = {
933
+ "model": "gpt-5-chat-latest",
934
+ "messages": messages,
935
+ "temperature": 0,
936
+ "max_tokens": 800,
937
+ "stream": False,
938
+ }
939
+
940
+ headers = {
941
+ "Content-Type": "application/json",
942
+ "Authorization": f"Bearer {OPENAI_API_KEY}"
943
+ }
944
+
945
+ response = requests.post(API_URL, headers=headers, json=payload)
946
+ if response.status_code == 200:
947
+ result = response.json()
948
+ return result['choices'][0]['message']['content']
949
+ else:
950
+ print(f"OpenAI API Error: {response.status_code} - {response.text}")
951
+ return f"API hatası: {response.status_code}. Lütfen daha sonra tekrar deneyin."
952
+
953
+ except Exception as e:
954
+ print(f"❌ WhatsApp mesaj işleme hatası: {e}")
955
+ return "Teknik bir sorun oluştu. Lütfen daha sonra tekrar deneyin."
956
+
957
+ # FastAPI uygulaması
958
+ app = FastAPI()
959
+
960
+ @app.post("/whatsapp-webhook")
961
+ async def whatsapp_webhook(request: Request):
962
+ try:
963
+ form_data = await request.form()
964
+
965
+ from_number = form_data.get('From')
966
+ to_number = form_data.get('To')
967
+ message_body = form_data.get('Body')
968
+ message_status = form_data.get('MessageStatus')
969
+
970
+ print(f"📱 Webhook - From: {from_number}, Body: {message_body}, Status: {message_status}")
971
+
972
+ # Durum güncellemelerini ignore et
973
+ if message_status in ['sent', 'delivered', 'read', 'failed']:
974
+ return {"status": "ignored", "message": f"Status: {message_status}"}
975
+
976
+ # Giden mesajları ignore et
977
+ if to_number != TWILIO_WHATSAPP_NUMBER:
978
+ return {"status": "ignored", "message": "Outgoing message"}
979
+
980
+ # Boş mesaj kontrolü
981
+ if not message_body or message_body.strip() == "":
982
+ return {"status": "ignored", "message": "Empty message"}
983
+
984
+ print(f"✅ MESAJ ALINDI: {from_number} -> {message_body}")
985
+
986
+ if not twilio_client:
987
+ return {"status": "error", "message": "Twilio yapılandırması eksik"}
988
+
989
+ # HAFIZALİ MESAJ İŞLEME
990
+ ai_response = process_whatsapp_message_with_memory(message_body, from_number)
991
+
992
+ # Mesajı parçalara böl
993
+ message_parts = split_long_message(ai_response, max_length=1600)
994
+
995
+ sent_messages = []
996
+
997
+ # Her parçayı sırayla gönder
998
+ for i, part in enumerate(message_parts):
999
+ if len(message_parts) > 1:
1000
+ if i == 0:
1001
+ part = f"{part}\n\n(1/{len(message_parts)})"
1002
+ elif i == len(message_parts) - 1:
1003
+ part = f"({i+1}/{len(message_parts)})\n\n{part}"
1004
+ else:
1005
+ part = f"({i+1}/{len(message_parts)})\n\n{part}"
1006
+
1007
+ # WhatsApp'a gönder
1008
+ message = twilio_client.messages.create(
1009
+ messaging_service_sid=TWILIO_MESSAGING_SERVICE_SID,
1010
+ body=part,
1011
+ to=from_number
1012
+ )
1013
+
1014
+ sent_messages.append(message.sid)
1015
+
1016
+ if i < len(message_parts) - 1:
1017
+ import time
1018
+ time.sleep(0.5)
1019
+
1020
+ print(f"✅ {len(message_parts)} PARÇA GÖNDERİLDİ")
1021
+
1022
+ # Debug için mevcut kategoriyi logla
1023
+ context = get_conversation_context(from_number)
1024
+ if context.get("current_category"):
1025
+ print(f"💭 Aktif kategori: {context['current_category']}")
1026
+
1027
+ return {
1028
+ "status": "success",
1029
+ "message_parts": len(message_parts),
1030
+ "message_sids": sent_messages,
1031
+ "current_category": context.get("current_category")
1032
+ }
1033
+
1034
+ except Exception as e:
1035
+ print(f"❌ Webhook hatası: {str(e)}")
1036
+ return {"status": "error", "message": str(e)}
1037
+
1038
+ @app.get("/")
1039
+ async def root():
1040
+ return {"message": "Trek WhatsApp Bot çalışıyor!", "status": "active"}
1041
+
1042
+ # Hafızayı temizleme endpoint'i
1043
+ @app.get("/clear-memory/{phone_number}")
1044
+ async def clear_memory(phone_number: str):
1045
+ """Belirli bir telefon numarasının hafızasını temizle"""
1046
+ if phone_number in conversation_memory:
1047
+ del conversation_memory[phone_number]
1048
+ return {"status": "success", "message": f"{phone_number} hafızası temizlendi"}
1049
+ return {"status": "info", "message": "Hafıza bulunamadı"}
1050
+
1051
+ # Tüm hafızayı görme endpoint'i
1052
+ @app.get("/debug-memory")
1053
+ async def debug_memory():
1054
+ """Tüm hafızay�� görüntüle (debug için)"""
1055
+ memory_info = {}
1056
+ for phone, context in conversation_memory.items():
1057
+ memory_info[phone] = {
1058
+ "current_category": context.get("current_category"),
1059
+ "message_count": len(context.get("messages", [])),
1060
+ "last_activity": str(context.get("last_activity"))
1061
+ }
1062
+ return {"conversation_memory": memory_info}
1063
+
1064
+ # Profil bilgilerini görme endpoint'i
1065
+ @app.get("/debug-profile/{phone_number}")
1066
+ async def debug_profile(phone_number: str):
1067
+ """Belirli kullanıcının profil bilgilerini görüntüle"""
1068
+ profile_summary = get_user_profile_summary(phone_number)
1069
+ return {"phone_number": phone_number, "profile": profile_summary}
1070
+
1071
+ # Tüm profilleri görme endpoint'i
1072
+ @app.get("/debug-profiles")
1073
+ async def debug_profiles():
1074
+ """Tüm kullanıcı profillerini görüntüle"""
1075
+ from whatsapp_passive_profiler import passive_profiler
1076
+ all_profiles = {}
1077
+ for phone_number in passive_profiler.profiles.keys():
1078
+ all_profiles[phone_number] = get_user_profile_summary(phone_number)
1079
+ return {"profiles": all_profiles}
1080
+
1081
+ @app.get("/health")
1082
+ async def health():
1083
+ return {
1084
+ "status": "healthy",
1085
+ "twilio_configured": twilio_client is not None,
1086
+ "openai_configured": OPENAI_API_KEY is not None,
1087
+ "products_loaded": len(products),
1088
+ "webhook_endpoint": "/whatsapp-webhook"
1089
+ }
1090
+
1091
+ if __name__ == "__main__":
1092
+ import uvicorn
1093
+ print("🚀 Trek WhatsApp Bot başlatılıyor...")
1094
+ uvicorn.run(app, host="0.0.0.0", port=7860)
product_search.py ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Professional Product Search Engine for Trek Chatbot
3
+ Implements intelligent product matching with fuzzy search and NLP techniques
4
+ """
5
+
6
+ import re
7
+ from difflib import SequenceMatcher
8
+ from typing import List, Tuple, Dict, Optional
9
+ import unicodedata
10
+
11
+ class ProductSearchEngine:
12
+ """Advanced product search with intelligent matching"""
13
+
14
+ def __init__(self, products: List[Tuple]):
15
+ """
16
+ Initialize with products list
17
+ products: List of tuples (short_name, product_info, full_name)
18
+ """
19
+ self.products = products
20
+ self.product_index = self._build_index()
21
+
22
+ def _build_index(self) -> Dict:
23
+ """Build search index for faster lookups"""
24
+ index = {
25
+ 'by_name': {},
26
+ 'by_words': {},
27
+ 'by_category': {},
28
+ 'by_model': {},
29
+ 'normalized': {}
30
+ }
31
+
32
+ for product in self.products:
33
+ short_name = product[0]
34
+ full_name = product[2]
35
+
36
+ # Normalize and store
37
+ normalized_full = self._normalize_text(full_name)
38
+ normalized_short = self._normalize_text(short_name)
39
+
40
+ # Store by full name
41
+ index['by_name'][normalized_full] = product
42
+ index['normalized'][normalized_full] = full_name
43
+
44
+ # Extract and index words
45
+ words = normalized_full.split()
46
+ for word in words:
47
+ if len(word) > 2: # Skip very short words
48
+ if word not in index['by_words']:
49
+ index['by_words'][word] = []
50
+ index['by_words'][word].append(product)
51
+
52
+ # Extract model numbers and categories
53
+ model_match = re.search(r'\b(\d+\.?\d*)\b', full_name)
54
+ if model_match:
55
+ model_num = model_match.group(1)
56
+ if model_num not in index['by_model']:
57
+ index['by_model'][model_num] = []
58
+ index['by_model'][model_num].append(product)
59
+
60
+ # Category extraction (first word often represents category)
61
+ if words:
62
+ category = words[0]
63
+ if category not in index['by_category']:
64
+ index['by_category'][category] = []
65
+ index['by_category'][category].append(product)
66
+
67
+ return index
68
+
69
+ def _normalize_text(self, text: str) -> str:
70
+ """Normalize text for better matching"""
71
+ if not text:
72
+ return ""
73
+
74
+ # Convert to lowercase
75
+ text = text.lower()
76
+
77
+ # Remove Turkish characters
78
+ replacements = {
79
+ 'ı': 'i', 'İ': 'i', 'ş': 's', 'Ş': 's',
80
+ 'ğ': 'g', 'Ğ': 'g', 'ü': 'u', 'Ü': 'u',
81
+ 'ö': 'o', 'Ö': 'o', 'ç': 'c', 'Ç': 'c'
82
+ }
83
+ for tr_char, eng_char in replacements.items():
84
+ text = text.replace(tr_char, eng_char)
85
+
86
+ # Remove special characters but keep spaces and numbers
87
+ text = re.sub(r'[^\w\s\d\.]', ' ', text)
88
+
89
+ # Normalize whitespace
90
+ text = ' '.join(text.split())
91
+
92
+ return text
93
+
94
+ def _calculate_similarity(self, str1: str, str2: str) -> float:
95
+ """Calculate similarity between two strings"""
96
+ return SequenceMatcher(None, str1, str2).ratio()
97
+
98
+ def search(self, query: str, threshold: float = 0.6) -> List[Tuple[float, Tuple]]:
99
+ """
100
+ Search for products matching the query
101
+ Returns list of (score, product) tuples sorted by relevance
102
+ """
103
+ query_normalized = self._normalize_text(query)
104
+ query_words = query_normalized.split()
105
+
106
+ results = {}
107
+
108
+ # 1. Exact match
109
+ if query_normalized in self.product_index['by_name']:
110
+ product = self.product_index['by_name'][query_normalized]
111
+ results[id(product)] = (1.0, product)
112
+
113
+ # 2. Model number search
114
+ model_match = re.search(r'\b(\d+\.?\d*)\b', query)
115
+ if model_match:
116
+ model_num = model_match.group(1)
117
+ if model_num in self.product_index['by_model']:
118
+ for product in self.product_index['by_model'][model_num]:
119
+ if id(product) not in results:
120
+ # Check if model number is in correct context
121
+ score = 0.9 if model_num in product[2].lower() else 0.7
122
+ results[id(product)] = (score, product)
123
+
124
+ # 3. Word-based search with scoring
125
+ word_matches = {}
126
+ for word in query_words:
127
+ if len(word) > 2 and word in self.product_index['by_words']:
128
+ for product in self.product_index['by_words'][word]:
129
+ if id(product) not in word_matches:
130
+ word_matches[id(product)] = {'count': 0, 'product': product}
131
+ word_matches[id(product)]['count'] += 1
132
+
133
+ # Calculate word match scores
134
+ for product_id, match_info in word_matches.items():
135
+ product = match_info['product']
136
+ matched_count = match_info['count']
137
+ total_query_words = len([w for w in query_words if len(w) > 2])
138
+
139
+ if total_query_words > 0:
140
+ word_score = matched_count / total_query_words
141
+
142
+ # Boost score if all important words match
143
+ if matched_count == total_query_words:
144
+ word_score = min(word_score * 1.2, 0.95)
145
+
146
+ # Check word order for better scoring
147
+ product_text = self._normalize_text(product[2])
148
+ if query_normalized in product_text:
149
+ word_score = min(word_score * 1.3, 0.98)
150
+
151
+ if id(product) not in results or results[id(product)][0] < word_score:
152
+ results[id(product)] = (word_score, product)
153
+
154
+ # 4. Fuzzy matching for all products
155
+ for product in self.products:
156
+ product_normalized = self._normalize_text(product[2])
157
+ similarity = self._calculate_similarity(query_normalized, product_normalized)
158
+
159
+ # Substring matching
160
+ if query_normalized in product_normalized:
161
+ similarity = max(similarity, 0.8)
162
+
163
+ # Check if product contains all query words (in any order)
164
+ if all(word in product_normalized for word in query_words if len(word) > 2):
165
+ similarity = max(similarity, 0.75)
166
+
167
+ if similarity >= threshold:
168
+ if id(product) not in results or results[id(product)][0] < similarity:
169
+ results[id(product)] = (similarity, product)
170
+
171
+ # 5. Category-based fallback
172
+ if not results and query_words:
173
+ category = query_words[0]
174
+ if category in self.product_index['by_category']:
175
+ for product in self.product_index['by_category'][category]:
176
+ results[id(product)] = (0.5, product)
177
+
178
+ # Convert to list and sort by score
179
+ result_list = list(results.values())
180
+ result_list.sort(key=lambda x: x[0], reverse=True)
181
+
182
+ return result_list
183
+
184
+ def find_best_match(self, query: str) -> Optional[Tuple]:
185
+ """Find the single best matching product"""
186
+ results = self.search(query)
187
+ if results and results[0][0] >= 0.6:
188
+ return results[0][1]
189
+ return None
190
+
191
+ def find_similar_products(self, product_name: str, limit: int = 5) -> List[Tuple]:
192
+ """Find products similar to the given product name"""
193
+ results = self.search(product_name)
194
+ similar = []
195
+
196
+ # Skip the first result if it's an exact match
197
+ start_idx = 1 if results and results[0][0] > 0.95 else 0
198
+
199
+ for score, product in results[start_idx:start_idx + limit]:
200
+ if score >= 0.5:
201
+ similar.append(product)
202
+
203
+ return similar
204
+
205
+ def extract_product_context(self, query: str) -> Dict:
206
+ """Extract context from query (size, color, type, etc.)"""
207
+ context = {
208
+ 'sizes': [],
209
+ 'colors': [],
210
+ 'types': [],
211
+ 'features': [],
212
+ 'price_range': None
213
+ }
214
+
215
+ # Size detection
216
+ size_patterns = [
217
+ r'\b(xs|s|m|l|xl|xxl|2xl|3xl)\b',
218
+ r'\b(\d{2})\b(?=\s*beden|\s*numara|$)', # 44, 46, etc.
219
+ r'\b(small|medium|large)\b'
220
+ ]
221
+ for pattern in size_patterns:
222
+ matches = re.findall(pattern, query.lower())
223
+ context['sizes'].extend(matches)
224
+
225
+ # Color detection
226
+ colors = ['siyah', 'beyaz', 'mavi', 'kirmizi', 'yesil', 'gri', 'turuncu',
227
+ 'black', 'white', 'blue', 'red', 'green', 'grey', 'gray', 'orange']
228
+ for color in colors:
229
+ if color in query.lower():
230
+ context['colors'].append(color)
231
+
232
+ # Type detection
233
+ types = ['erkek', 'kadin', 'cocuk', 'yol', 'dag', 'sehir', 'elektrikli',
234
+ 'karbon', 'aluminyum', 'gravel', 'hybrid']
235
+ for type_word in types:
236
+ if type_word in query.lower():
237
+ context['types'].append(type_word)
238
+
239
+ # Feature detection
240
+ features = ['disk fren', 'shimano', 'sram', 'karbon', 'aluminyum',
241
+ 'hidrolik', 'mekanik', '29 jant', '27.5 jant']
242
+ for feature in features:
243
+ if feature in query.lower():
244
+ context['features'].append(feature)
245
+
246
+ # Price range detection
247
+ price_match = re.search(r'(\d+)\.?(\d*)\s*(bin|tl)', query.lower())
248
+ if price_match:
249
+ price = float(price_match.group(1) + ('.' + price_match.group(2) if price_match.group(2) else ''))
250
+ if 'bin' in price_match.group(3):
251
+ price *= 1000
252
+ context['price_range'] = price
253
+
254
+ return context
255
+
256
+ def generate_suggestions(self, failed_query: str) -> List[str]:
257
+ """Generate suggestions for failed searches"""
258
+ suggestions = []
259
+ query_normalized = self._normalize_text(failed_query)
260
+ query_words = query_normalized.split()
261
+
262
+ # Find products with partial matches
263
+ partial_matches = set()
264
+ for word in query_words:
265
+ if len(word) > 3:
266
+ for product_word in self.product_index['by_words']:
267
+ if word in product_word or product_word in word:
268
+ partial_matches.add(product_word)
269
+
270
+ # Generate suggestions from partial matches
271
+ for match in list(partial_matches)[:5]:
272
+ if match in self.product_index['by_words']:
273
+ products = self.product_index['by_words'][match]
274
+ if products:
275
+ suggestions.append(products[0][2])
276
+
277
+ # Add category suggestions
278
+ for category in list(self.product_index['by_category'].keys())[:3]:
279
+ if any(word in category for word in query_words):
280
+ category_products = self.product_index['by_category'][category]
281
+ if category_products:
282
+ suggestions.append(category_products[0][2])
283
+
284
+ return list(set(suggestions))[:5] # Return unique suggestions
whatsapp_improved_chatbot.py ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ WhatsApp Improved Chatbot with Professional Product Search
3
+ Optimized for mobile WhatsApp formatting
4
+ """
5
+
6
+ from product_search import ProductSearchEngine
7
+ import re
8
+ from typing import List, Dict, Optional, Tuple
9
+
10
+ class WhatsAppImprovedChatbot:
11
+ """Enhanced chatbot with intelligent product handling for WhatsApp"""
12
+
13
+ def __init__(self, products: List[Tuple]):
14
+ """Initialize with product list"""
15
+ self.search_engine = ProductSearchEngine(products)
16
+ self.products = products
17
+
18
+ def detect_product_query(self, message: str) -> bool:
19
+ """Detect if message is asking about products"""
20
+ product_keywords = [
21
+ 'bisiklet', 'bike', 'model', 'fiyat', 'price', 'stok', 'stock',
22
+ 'özellikleri', 'features', 'hangi', 'which', 'var mı', 'mevcut',
23
+ 'kaç', 'how much', 'ne kadar', 'kampanya', 'indirim', 'discount',
24
+ 'karşılaştır', 'compare', 'benzer', 'similar', 'alternatif',
25
+ 'öneri', 'recommend', 'tavsiye', 'suggest'
26
+ ]
27
+
28
+ message_lower = message.lower()
29
+
30
+ # Check for product keywords
31
+ if any(keyword in message_lower for keyword in product_keywords):
32
+ return True
33
+
34
+ # Check for model numbers
35
+ if re.search(r'\b\d+\.?\d*\b', message):
36
+ return True
37
+
38
+ # Check for known product categories
39
+ categories = ['trek', 'marlin', 'fuel', 'slash', 'remedy', 'checkpoint',
40
+ 'domane', 'madone', 'emonda', 'fx', 'dual', 'wahoo', 'powerfly']
41
+ if any(cat in message_lower for cat in categories):
42
+ return True
43
+
44
+ return False
45
+
46
+ def extract_query_intent(self, message: str) -> Dict:
47
+ """Extract the intent from user query"""
48
+ intent = {
49
+ 'type': 'general',
50
+ 'action': None,
51
+ 'filters': {}
52
+ }
53
+
54
+ message_lower = message.lower()
55
+
56
+ # Price query
57
+ if any(word in message_lower for word in ['fiyat', 'price', 'kaç', 'ne kadar', 'how much']):
58
+ intent['type'] = 'price'
59
+ intent['action'] = 'get_price'
60
+
61
+ # Stock query
62
+ elif any(word in message_lower for word in ['stok', 'stock', 'var mı', 'mevcut', 'available']):
63
+ intent['type'] = 'stock'
64
+ intent['action'] = 'check_stock'
65
+
66
+ # Comparison query
67
+ elif any(word in message_lower for word in ['karşılaştır', 'compare', 'fark', 'difference']):
68
+ intent['type'] = 'comparison'
69
+ intent['action'] = 'compare_products'
70
+
71
+ # Recommendation query
72
+ elif any(word in message_lower for word in ['öneri', 'recommend', 'tavsiye', 'suggest', 'hangi']):
73
+ intent['type'] = 'recommendation'
74
+ intent['action'] = 'recommend'
75
+
76
+ # Feature query
77
+ elif any(word in message_lower for word in ['özellik', 'feature', 'spec', 'detay', 'detail']):
78
+ intent['type'] = 'features'
79
+ intent['action'] = 'get_features'
80
+
81
+ # Extract context (size, color, type, etc.)
82
+ context = self.search_engine.extract_product_context(message)
83
+ intent['filters'] = context
84
+
85
+ return intent
86
+
87
+ def format_product_info_whatsapp(self, product: Tuple, intent: Dict) -> str:
88
+ """Format product information for WhatsApp"""
89
+ name = product[2] # Full name
90
+ info = product[1] # Product info array
91
+
92
+ # Basic info
93
+ stock_status = info[0] if len(info) > 0 else "bilinmiyor"
94
+
95
+ # Build response for WhatsApp (more compact format)
96
+ response_parts = []
97
+
98
+ # Product name with status emoji
99
+ if stock_status == "stokta":
100
+ response_parts.append(f"🚴‍♂️ *{name}*")
101
+ response_parts.append("✅ *Stokta mevcut*")
102
+ else:
103
+ response_parts.append(f"🚴‍♂️ *{name}*")
104
+ response_parts.append("❌ *Stokta yok*")
105
+
106
+ # Price information (only if in stock)
107
+ if intent['type'] in ['price', 'general'] and stock_status == "stokta" and len(info) > 1:
108
+ price = info[1]
109
+ response_parts.append(f"💰 *Fiyat:* {price} TL")
110
+
111
+ # Campaign price if available
112
+ if len(info) > 4 and info[4]:
113
+ response_parts.append(f"🎯 *Kampanya:* {info[4]} TL")
114
+
115
+ # Calculate discount
116
+ try:
117
+ normal_price = float(info[1])
118
+ campaign_price = float(info[4])
119
+ discount = normal_price - campaign_price
120
+ if discount > 0:
121
+ response_parts.append(f"💸 *İndirim:* {discount:.0f} TL")
122
+ except:
123
+ pass
124
+
125
+ # Wire transfer price if no campaign
126
+ elif len(info) > 3 and info[3]:
127
+ response_parts.append(f"🏦 *Havale:* {info[3]} TL")
128
+
129
+ # Product link (shortened for WhatsApp)
130
+ if len(info) > 2 and info[2]:
131
+ response_parts.append(f"🔗 Ürün sayfası: {info[2]}")
132
+
133
+ return "\n".join(response_parts)
134
+
135
+ def generate_product_response(self, message: str) -> str:
136
+ """Generate response for product queries optimized for WhatsApp"""
137
+ intent = self.extract_query_intent(message)
138
+
139
+ # Search for products
140
+ search_results = self.search_engine.search(message)
141
+
142
+ if not search_results:
143
+ # No products found - provide helpful response
144
+ response = self._handle_no_results_whatsapp(message)
145
+ elif len(search_results) == 1:
146
+ # Single product found
147
+ product = search_results[0][1]
148
+ response = self.format_product_info_whatsapp(product, intent)
149
+ elif search_results[0][0] > 0.9:
150
+ # Very high confidence match
151
+ product = search_results[0][1]
152
+ response = self.format_product_info_whatsapp(product, intent)
153
+ else:
154
+ # Multiple products found
155
+ response = self._handle_multiple_results_whatsapp(search_results[:3], intent) # Only 3 for WhatsApp
156
+
157
+ return response
158
+
159
+ def _handle_no_results_whatsapp(self, query: str) -> str:
160
+ """Handle case when no products are found - WhatsApp format"""
161
+ response_parts = ["🤔 *Aradığınız ürünü bulamadım.*"]
162
+
163
+ # Get suggestions
164
+ suggestions = self.search_engine.generate_suggestions(query)
165
+
166
+ if suggestions:
167
+ response_parts.append("\n💡 *Belki şunları arıyor olabilirsiniz:*")
168
+ for i, suggestion in enumerate(suggestions[:2], 1): # Only 2 suggestions for WhatsApp
169
+ response_parts.append(f"{i}. {suggestion}")
170
+ else:
171
+ response_parts.append("\n📝 *Örnek aramalar:*")
172
+ response_parts.append("• Marlin 5")
173
+ response_parts.append("• FX 3 Disc")
174
+ response_parts.append("• Checkpoint ALR 5")
175
+
176
+ return "\n".join(response_parts)
177
+
178
+ def _handle_multiple_results_whatsapp(self, results: List[Tuple[float, Tuple]], intent: Dict) -> str:
179
+ """Handle multiple product results - WhatsApp format"""
180
+ response_parts = ["📋 *Birden fazla ürün buldum:*\n"]
181
+
182
+ for i, (score, product) in enumerate(results, 1):
183
+ name = product[2]
184
+ stock = product[1][0] if len(product[1]) > 0 else "bilinmiyor"
185
+
186
+ # Compact info line for WhatsApp
187
+ if stock == "stokta":
188
+ status_emoji = "✅"
189
+ response_parts.append(f"*{i}. {name}* {status_emoji}")
190
+
191
+ # Add price if intent is price-related
192
+ if intent['type'] in ['price', 'general'] and len(product[1]) > 1:
193
+ price = product[1][1]
194
+ response_parts.append(f" 💰 {price} TL")
195
+
196
+ # Show campaign price if available
197
+ if len(product[1]) > 4 and product[1][4]:
198
+ response_parts.append(f" 🎯 Kampanya: {product[1][4]} TL")
199
+
200
+ # Add product link if available
201
+ if len(product[1]) > 2 and product[1][2]:
202
+ response_parts.append(f" 🔗 {product[1][2]}")
203
+ else:
204
+ status_emoji = "❌"
205
+ response_parts.append(f"*{i}. {name}* {status_emoji}")
206
+
207
+ response_parts.append("") # Empty line between products
208
+
209
+ response_parts.append("💡 _Daha detaylı bilgi için ürün adını yazın_")
210
+
211
+ return "\n".join(response_parts)
212
+
213
+ def handle_comparison_whatsapp(self, message: str) -> Optional[str]:
214
+ """Handle product comparison requests - WhatsApp format"""
215
+ # Extract product names for comparison
216
+ comparison_words = ['ile', 've', 'vs', 'versus', 'karşılaştır', 'arasında']
217
+
218
+ products_to_compare = []
219
+
220
+ # Try to extract two product names
221
+ for word in comparison_words:
222
+ if word in message.lower():
223
+ parts = message.lower().split(word)
224
+ if len(parts) >= 2:
225
+ # Search for each part
226
+ for part in parts[:2]:
227
+ result = self.search_engine.find_best_match(part.strip())
228
+ if result:
229
+ products_to_compare.append(result)
230
+
231
+ if len(products_to_compare) < 2:
232
+ # Try to find products mentioned in the message
233
+ search_results = self.search_engine.search(message)
234
+ products_to_compare = [r[1] for r in search_results[:2] if r[0] > 0.6]
235
+
236
+ if len(products_to_compare) >= 2:
237
+ return self._format_comparison_whatsapp(products_to_compare[:2])
238
+
239
+ return None
240
+
241
+ def _format_comparison_whatsapp(self, products: List[Tuple]) -> str:
242
+ """Format product comparison for WhatsApp"""
243
+ response_parts = ["📊 *Ürün Karşılaştırması*\n"]
244
+
245
+ for i, product in enumerate(products, 1):
246
+ name = product[2]
247
+ info = product[1]
248
+
249
+ response_parts.append(f"*{i}. {name}*")
250
+
251
+ # Stock status
252
+ stock = info[0] if len(info) > 0 else "bilinmiyor"
253
+ if stock == "stokta":
254
+ response_parts.append("✅ Stokta mevcut")
255
+ else:
256
+ response_parts.append("❌ Stokta yok")
257
+
258
+ # Price (compact for WhatsApp)
259
+ if stock == "stokta" and len(info) > 1:
260
+ price = info[1]
261
+ response_parts.append(f"💰 {price} TL")
262
+
263
+ if len(info) > 4 and info[4]:
264
+ response_parts.append(f"🎯 Kampanya: {info[4]} TL")
265
+
266
+ response_parts.append("") # Empty line
267
+
268
+ return "\n".join(response_parts)
269
+
270
+ def process_message(self, message: str) -> Dict:
271
+ """Process user message and return structured response optimized for WhatsApp"""
272
+ result = {
273
+ 'is_product_query': False,
274
+ 'response': None,
275
+ 'products_found': [],
276
+ 'intent': None
277
+ }
278
+
279
+ # Check if it's a product query
280
+ if self.detect_product_query(message):
281
+ result['is_product_query'] = True
282
+ result['intent'] = self.extract_query_intent(message)
283
+
284
+ # Check for comparison request
285
+ if result['intent']['type'] == 'comparison':
286
+ comparison_result = self.handle_comparison_whatsapp(message)
287
+ if comparison_result:
288
+ result['response'] = comparison_result
289
+ return result
290
+
291
+ # Generate product response
292
+ result['response'] = self.generate_product_response(message)
293
+
294
+ # Get found products
295
+ search_results = self.search_engine.search(message)
296
+ result['products_found'] = [r[1] for r in search_results[:2] if r[0] > 0.6] # Limit to 2 for WhatsApp
297
+
298
+ return result