SamiKoen commited on
Commit
d2af964
·
1 Parent(s): 5f78ffc

Revert to backup - removing AI modifications

Browse files
__pycache__/smart_warehouse_with_price.cpython-312.pyc CHANGED
Binary files a/__pycache__/smart_warehouse_with_price.cpython-312.pyc and b/__pycache__/smart_warehouse_with_price.cpython-312.pyc differ
 
ai_product_analyzer.py DELETED
@@ -1,328 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
-
4
- """
5
- GPT-5 Powered Product & Query Analyzer
6
- Tüm manuel kontrolleri AI ile otomatikleştirir
7
- """
8
-
9
- import os
10
- import json
11
- import requests
12
- import logging
13
- from typing import Dict, Optional, List, Tuple
14
-
15
- logger = logging.getLogger(__name__)
16
-
17
- # OpenAI API
18
- OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
19
- API_URL = "https://api.openai.com/v1/chat/completions"
20
-
21
- def analyze_product_query(
22
- message: str,
23
- products_data: Optional[List[Dict]] = None,
24
- context: Optional[Dict] = None
25
- ) -> Dict:
26
- """
27
- GPT-5 ile ürün sorgusunu analiz et
28
-
29
- Args:
30
- message: Kullanıcı mesajı
31
- products_data: Mevcut ürün listesi
32
- context: Sohbet bağlamı
33
-
34
- Returns:
35
- {
36
- "is_product_query": bool, # Ürün sorusu mu?
37
- "product_name": str, # Aranan ürün adı
38
- "exact_matches": [], # Tam eşleşen ürünler
39
- "partial_matches": [], # Kısmi eşleşen ürünler
40
- "category": str, # Bisiklet kategorisi (dağ/yol/şehir/elektrikli)
41
- "price_range": {
42
- "min": float,
43
- "max": float,
44
- "condition": str # "altında", "üstünde", "arası"
45
- },
46
- "store_preference": str, # Tercih edilen mağaza
47
- "is_greeting": bool, # Selamlama mı?
48
- "intent": str, # Kullanıcı niyeti
49
- "confidence": float
50
- }
51
- """
52
-
53
- if not OPENAI_API_KEY:
54
- logger.error("OpenAI API key missing")
55
- return {"is_product_query": False, "confidence": 0}
56
-
57
- # Sistem promptu
58
- system_prompt = """Sen bir bisiklet ürün analiz uzmanısın. Trek bisiklet mağazası için sorguları analiz ediyorsun.
59
-
60
- GÖREV: Kullanıcı mesajını analiz et ve JSON formatında döndür.
61
-
62
- ÜRÜN EŞLEŞTİRME KURALLARI:
63
- 1. Tam eşleşme: Kullanıcı "FX 2" derse, sadece "FX 2"yi döndür, "FX Sport 6"yı değil
64
- 2. Model numarası önemli: "Marlin 5" ve "Marlin 7" farklı ürünlerdir
65
- 3. Seri adı yeterli değilse tüm modelleri listele
66
-
67
- KATEGORİ TESPİTİ:
68
- - "dağ bisikleti", "mountain bike" → category: "dağ"
69
- - "yol bisikleti", "road bike" → category: "yol"
70
- - "şehir bisikleti", "urban" → category: "şehir"
71
- - "elektrikli", "e-bike" → category: "elektrikli"
72
-
73
- FİYAT ARALIKLARI:
74
- - "50 bin altı", "50000 altında" → max: 50000
75
- - "100 bin üstü" → min: 100000
76
- - "50-100 bin arası" → min: 50000, max: 100000
77
-
78
- NON-PRODUCT KELİMELER (ürün sorgusu değil):
79
- - "süper", "harika", "teşekkürler", "güzel", "tamam", "olur", "evet", "hayır"
80
- - Bu kelimeler tek başına gelirse is_product_query: false
81
-
82
- SELAMLAMA TESPİTİ:
83
- - "merhaba", "selam", "günaydın", "iyi akşamlar" → is_greeting: true
84
-
85
- JSON FORMATI:
86
- {
87
- "is_product_query": true/false,
88
- "product_name": "FX 2", // Eğer ürün adı varsa
89
- "category": "şehir", // dağ/yol/şehir/elektrikli/gravel
90
- "price_range": {
91
- "min": 0,
92
- "max": 100000,
93
- "condition": "altında"
94
- },
95
- "store_preference": "caddebostan", // Eğer mağaza belirtildiyse
96
- "is_greeting": false,
97
- "intent": "product_info", // product_info, price_check, stock_check, general_question
98
- "confidence": 0.95,
99
- "search_terms": ["fx", "2"], // Arama için kullanılacak terimler
100
- "requires_exact_match": true // Tam eşleşme gerekli mi?
101
- }"""
102
-
103
- # Ürün listesi varsa ekle
104
- product_info = ""
105
- if products_data and len(products_data) > 0:
106
- # Sadece ilk 20 ürünü göster (token limiti için)
107
- sample_products = products_data[:20]
108
- product_names = [p.get("name", "") for p in sample_products]
109
- product_info = f"\n\nMEVCUT ÜRÜNLER (örnek): {', '.join(product_names)}"
110
-
111
- # GPT-5'e gönder
112
- try:
113
- messages = [
114
- {"role": "system", "content": system_prompt},
115
- {"role": "user", "content": f"Kullanıcı mesajı: \"{message}\"{product_info}\n\nJSON formatında analiz et:"}
116
- ]
117
-
118
- payload = {
119
- "model": "gpt-5-chat-latest",
120
- "messages": messages,
121
- "temperature": 0,
122
- "max_tokens": 500,
123
- "response_format": {"type": "json_object"}
124
- }
125
-
126
- headers = {
127
- "Content-Type": "application/json",
128
- "Authorization": f"Bearer {OPENAI_API_KEY}"
129
- }
130
-
131
- response = requests.post(API_URL, headers=headers, json=payload, timeout=10)
132
-
133
- if response.status_code == 200:
134
- result = response.json()
135
- analysis = json.loads(result['choices'][0]['message']['content'])
136
-
137
- logger.info(f"🤖 Product Analysis: {analysis}")
138
- return analysis
139
- else:
140
- logger.error(f"GPT-5 API error: {response.status_code}")
141
- return {"is_product_query": False, "confidence": 0}
142
-
143
- except Exception as e:
144
- logger.error(f"Product analysis error: {e}")
145
- return {"is_product_query": False, "confidence": 0}
146
-
147
- def smart_product_search(
148
- analysis: Dict,
149
- products_data: List[Dict]
150
- ) -> List[Dict]:
151
- """
152
- AI analiz sonucuna göre akıllı ürün arama
153
-
154
- Args:
155
- analysis: GPT-5 analiz sonucu
156
- products_data: Tüm ürün listesi
157
-
158
- Returns:
159
- Eşleşen ürünler listesi
160
- """
161
-
162
- if not analysis.get("is_product_query"):
163
- return []
164
-
165
- results = []
166
-
167
- # Arama terimlerini al
168
- search_terms = analysis.get("search_terms", [])
169
- product_name = analysis.get("product_name", "").lower()
170
- category = analysis.get("category", "").lower()
171
- price_range = analysis.get("price_range", {})
172
- requires_exact = analysis.get("requires_exact_match", False)
173
-
174
- for product in products_data:
175
- item_name = product.get("name", "").lower()
176
- item_category = product.get("category", "").lower()
177
- item_price = product.get("price", 0)
178
-
179
- # Skor hesaplama
180
- score = 0
181
-
182
- # Tam eşleşme kontrolü
183
- if requires_exact and product_name:
184
- if item_name == product_name:
185
- score += 100
186
- elif not item_name.startswith(product_name + " "):
187
- continue # Tam eşleşme isteniyorsa ve yoksa atla
188
-
189
- # Kategori eşleşmesi
190
- if category:
191
- if category in item_category or category in item_name:
192
- score += 20
193
- else:
194
- continue # Kategori belirtildiyse ve eşleşmiyorsa atla
195
-
196
- # Fiyat aralığı kontrolü
197
- if price_range:
198
- min_price = price_range.get("min", 0)
199
- max_price = price_range.get("max", float('inf'))
200
-
201
- if min_price <= item_price <= max_price:
202
- score += 10
203
- else:
204
- continue # Fiyat aralığı dışındaysa atla
205
-
206
- # Arama terimleri kontrolü
207
- if search_terms:
208
- matches = sum(1 for term in search_terms if term.lower() in item_name)
209
- score += matches * 15
210
-
211
- # Sonuçlara ekle
212
- if score > 0:
213
- product["match_score"] = score
214
- results.append(product)
215
-
216
- # Skora göre sırala
217
- results.sort(key=lambda x: x.get("match_score", 0), reverse=True)
218
-
219
- return results[:10] # En iyi 10 sonucu döndür
220
-
221
- def analyze_complete_query(
222
- message: str,
223
- products_data: Optional[List[Dict]] = None,
224
- context: Optional[Dict] = None
225
- ) -> Dict:
226
- """
227
- Komple sorgu analizi - tüm manuel kontrolleri birleştirir
228
-
229
- Returns:
230
- {
231
- "analysis": {...}, # GPT-5 analiz sonucu
232
- "products": [...], # Bulunan ürünler
233
- "action": "show_products/greet/answer/notify_store",
234
- "response_hint": "Önerilen yanıt yaklaşımı"
235
- }
236
- """
237
-
238
- # 1. AI analizi yap
239
- analysis = analyze_product_query(message, products_data, context)
240
-
241
- # 2. Ürün araması yap
242
- products = []
243
- if analysis.get("is_product_query") and products_data:
244
- products = smart_product_search(analysis, products_data)
245
-
246
- # 3. Aksiyon belirle
247
- action = "answer" # Varsayılan
248
- response_hint = ""
249
-
250
- if analysis.get("is_greeting"):
251
- action = "greet"
252
- response_hint = "Samimi bir selamlama yap"
253
- elif analysis.get("is_product_query"):
254
- if products:
255
- action = "show_products"
256
- response_hint = f"Bulunan {len(products)} ürünü göster"
257
- else:
258
- action = "no_products"
259
- response_hint = "Ürün bulunamadı, alternatif öner"
260
- elif analysis.get("intent") == "price_check":
261
- action = "show_price"
262
- response_hint = "Fiyat bilgisi ver"
263
- elif analysis.get("intent") == "stock_check":
264
- action = "check_stock"
265
- response_hint = "Stok durumunu kontrol et"
266
-
267
- return {
268
- "analysis": analysis,
269
- "products": products,
270
- "action": action,
271
- "response_hint": response_hint,
272
- "confidence": analysis.get("confidence", 0)
273
- }
274
-
275
- # Test fonksiyonu
276
- def test_ai_analyzer():
277
- """Test senaryoları"""
278
-
279
- test_messages = [
280
- "FX 2 var mı?",
281
- "Marlin 5'in fiyatı nedir?",
282
- "50 bin altı dağ bisikleti var mı?",
283
- "süper",
284
- "merhaba",
285
- "caddebostan mağazasında elektrikli bisiklet var mı?",
286
- "100-200 bin arası yol bisikleti önerir misiniz?",
287
- "teşekkürler"
288
- ]
289
-
290
- # Örnek ürün listesi
291
- sample_products = [
292
- {"name": "FX 2", "price": 65000, "category": "şehir"},
293
- {"name": "FX Sport 6", "price": 183000, "category": "şehir"},
294
- {"name": "Marlin 5", "price": 53000, "category": "dağ"},
295
- {"name": "Marlin 7", "price": 72000, "category": "dağ"},
296
- {"name": "Domane AL 2", "price": 110000, "category": "yol"},
297
- {"name": "Powerfly 4", "price": 220000, "category": "elektrikli dağ"}
298
- ]
299
-
300
- print("\n" + "="*60)
301
- print("AI PRODUCT ANALYZER TEST")
302
- print("="*60)
303
-
304
- for msg in test_messages:
305
- print(f"\n📝 Mesaj: \"{msg}\"")
306
- result = analyze_complete_query(msg, sample_products)
307
-
308
- analysis = result["analysis"]
309
- print(f"🤖 Ürün Sorgusu: {'EVET' if analysis.get('is_product_query') else 'HAYIR'}")
310
- print(f"🎯 Niyet: {analysis.get('intent', 'unknown')}")
311
- print(f"📊 Güven: {analysis.get('confidence', 0):.2%}")
312
-
313
- if analysis.get("product_name"):
314
- print(f"🚲 Aranan: {analysis['product_name']}")
315
-
316
- if result["products"]:
317
- print(f"✅ Bulunan: {len(result['products'])} ürün")
318
- for p in result["products"][:3]:
319
- print(f" - {p['name']}: {p['price']:,} TL")
320
-
321
- print(f"🎬 Aksiyon: {result['action']}")
322
- if result["response_hint"]:
323
- print(f"💡 Öneri: {result['response_hint']}")
324
-
325
- print("\n" + "="*60)
326
-
327
- if __name__ == "__main__":
328
- test_ai_analyzer()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py CHANGED
@@ -32,23 +32,14 @@ USE_IMPROVED_SEARCH = False
32
  # print("Improved WhatsApp chatbot not available, using basic search")
33
  # USE_IMPROVED_SEARCH = False
34
 
35
- # Import GPT-5 powered smart warehouse search - AI-powered version
36
  try:
37
- from smart_warehouse_with_price_ai import search_products_smart_ai, format_stock_message
38
  from smart_warehouse_with_price import get_warehouse_stock_smart_with_price
39
  USE_GPT5_SEARCH = True
40
- USE_AI_SEARCH = True
41
- logger.info("✅ AI-powered smart warehouse loaded")
42
  except ImportError:
43
- try:
44
- from smart_warehouse_with_price import get_warehouse_stock_smart_with_price
45
- USE_GPT5_SEARCH = True
46
- USE_AI_SEARCH = False
47
- logger.info("✅ GPT-5 complete smart warehouse with price (BF algorithm) loaded")
48
- except ImportError:
49
- USE_GPT5_SEARCH = False
50
- USE_AI_SEARCH = False
51
- logger.info("❌ GPT-5 search not available")
52
 
53
  warnings.simplefilter('ignore')
54
 
@@ -116,15 +107,9 @@ else:
116
  twilio_client = None
117
 
118
  # Mağaza stok bilgilerini çekme fonksiyonu
119
- def get_warehouse_stock(product_name, context=None):
120
- """B2B API'den mağaza stok bilgilerini çek - AI-enhanced"""
121
- # Try AI-powered search first
122
- if USE_AI_SEARCH:
123
- ai_result = search_products_smart_ai(product_name, context)
124
- if ai_result and isinstance(ai_result, list):
125
- return ai_result
126
-
127
- # Fallback to GPT-5 complete smart search (BF algorithm)
128
  if USE_GPT5_SEARCH:
129
  gpt5_result = get_warehouse_stock_smart_with_price(product_name)
130
  if gpt5_result and isinstance(gpt5_result, list):
 
32
  # print("Improved WhatsApp chatbot not available, using basic search")
33
  # USE_IMPROVED_SEARCH = False
34
 
35
+ # Import GPT-5 powered smart warehouse search - complete BF algorithm
36
  try:
 
37
  from smart_warehouse_with_price import get_warehouse_stock_smart_with_price
38
  USE_GPT5_SEARCH = True
39
+ logger.info("✅ GPT-5 complete smart warehouse with price (BF algorithm) loaded")
 
40
  except ImportError:
41
+ USE_GPT5_SEARCH = False
42
+ logger.info("❌ GPT-5 search not available")
 
 
 
 
 
 
 
43
 
44
  warnings.simplefilter('ignore')
45
 
 
107
  twilio_client = None
108
 
109
  # Mağaza stok bilgilerini çekme fonksiyonu
110
+ def get_warehouse_stock(product_name):
111
+ """B2B API'den mağaza stok bilgilerini çek - GPT-5 enhanced"""
112
+ # Try GPT-5 complete smart search (BF algorithm)
 
 
 
 
 
 
113
  if USE_GPT5_SEARCH:
114
  gpt5_result = get_warehouse_stock_smart_with_price(product_name)
115
  if gpt5_result and isinstance(gpt5_result, list):
debug_xml_fields.py DELETED
@@ -1,59 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Debug XML field names"""
3
-
4
- import requests
5
- import xml.etree.ElementTree as ET
6
-
7
- def debug_xml_structure():
8
- """Check XML structure and field names"""
9
- print("\n=== XML STRUCTURE DEBUG ===")
10
-
11
- try:
12
- # Get Trek XML
13
- url = 'https://www.trekbisiklet.com.tr/output/8582384479'
14
- response = requests.get(url, verify=False, timeout=10)
15
-
16
- if response.status_code == 200:
17
- root = ET.fromstring(response.content)
18
-
19
- # Find first Marlin 5 and show all fields
20
- for item in root.findall('.//item'):
21
- title_elem = item.find('rootlabel')
22
- if title_elem is not None and title_elem.text:
23
- if 'marlin 5' in title_elem.text.lower():
24
- print(f"\nProduct: {title_elem.text}")
25
- print("Available fields:")
26
-
27
- # Show all child elements
28
- for child in item:
29
- value = child.text if child.text else 'None'
30
- if len(value) > 100:
31
- value = value[:100] + '...'
32
- print(f" - {child.tag}: {value}")
33
-
34
- # Only show first one
35
- break
36
-
37
- # Also check different product for comparison
38
- print("\n" + "="*50)
39
- print("Checking a product with known price (FX 2):")
40
- for item in root.findall('.//item'):
41
- title_elem = item.find('rootlabel')
42
- if title_elem is not None and title_elem.text:
43
- if 'fx 2' in title_elem.text.lower() and 'sport' not in title_elem.text.lower():
44
- print(f"\nProduct: {title_elem.text}")
45
- print("Available fields:")
46
-
47
- for child in item:
48
- value = child.text if child.text else 'None'
49
- if len(value) > 100:
50
- value = value[:100] + '...'
51
- print(f" - {child.tag}: {value}")
52
-
53
- break
54
-
55
- except Exception as e:
56
- print(f"Error: {e}")
57
-
58
- if __name__ == "__main__":
59
- debug_xml_structure()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
prompts.py CHANGED
@@ -9,7 +9,7 @@ SYSTEM_PROMPTS = [
9
  {
10
  "role": "system",
11
  "category": "identity",
12
- "content": "Sen Trek bisiklet uzmanı AI asistanısın. Trek ve Electra bisikletler konusunda uzmanısın. Stokta bulunan ürünlerin fiyat bilgilerini verebilirsin.\n\n🚨🚨🚨 KRİTİK FİYAT VE LİNK KURALI 🚨🚨🚨\n\n1. FİYAT KURALI:\n• FİYAT BİLGİSİ YALNIZCA SİSTEM MESAJINDAN GELİR!\n Sistem mesajında fiyat yoksa: 'Güncel fiyat için mağazalarımızı arayın'\n ASLA TAHMİNİ FİYAT VERME!\n\n2. DOĞRU FİYAT ARALIKLARI:\n• Marlin 4-5-6-7: 40.000-80.000 TL arası\n• FX 1-2-3: 50.000-100.000 TL arası\n• FX Sport 4-5-6: 120.000-200.000 TL arası\n• Domane AL: 80.000-150.000 TL\n• Domane SL: 150.000-400.000 TL\n• Rail (elektrikli): 300.000-800.000 TL\n\n3. ÜRÜN-LİNK EŞLEŞMESİ:\n• Marlin için ASLA Rail linki verme!\n• FX 2 için ASLA FX Sport 6 linki verme!\n• Link yanlışsa hiç verme!\n\n❌ YANLIŞ: Marlin 5 - 700.000 TL - Rail linki\n✅ DOĞRU: Marlin 5 - 53.000 TL - Marlin 5 linki\n\nEĞER YANLIŞ FİYAT VEYA LİNK VARSA, HİÇ VERME!"
13
  },
14
 
15
  # 2. MAĞAZA BİLGİLERİ VE İLETİŞİM
 
9
  {
10
  "role": "system",
11
  "category": "identity",
12
+ "content": "Sen Trek bisiklet uzmanı AI asistanısın. Trek ve Electra bisikletler konusunda uzmanısın. Stokta bulunan ürünlerin fiyat bilgilerini verebilirsin.\n\n🚨🚨🚨 MUTLAK FİYAT KURALI 🚨🚨🚨\nFİYAT BİLGİSİ YALNIZCA 'MAĞAZA STOK BİLGİSİ' BAŞLIKLI SİSTEM MESAJINDAN GELİR!\n\nEĞER SİSTEM MESAJINDA FİYAT YOKSA:\n✅ DOĞRU CEVAP: 'Güncel fiyat bilgisi için mağazalarımızı arayın'\n YANLIŞ CEVAP: Herhangi bir fiyat söylemek (700.000 TL gibi)\n\nGERÇEK FİYAT ARALIKLARI:\n• Marlin serisi: 50.000-80.000 TL\n• FX serisi: 50.000-200.000 TL\n• Domane/Émonda: 100.000-600.000 TL\n• Madone: 200.000-1.000.000 TL\n\n700.000 TL bir Marlin için ABSÜRT bir fiyattır! ASLA böyle fiyatlar verme!"
13
  },
14
 
15
  # 2. MAĞAZA BİLGİLERİ VE İLETİŞİM
smart_warehouse_with_price.py CHANGED
@@ -6,11 +6,6 @@ import os
6
  import json
7
  import xml.etree.ElementTree as ET
8
  import time
9
- from universal_product_matcher import find_best_match, normalize_text
10
- try:
11
- from ai_product_analyzer import analyze_complete_query
12
- except ImportError:
13
- analyze_complete_query = None
14
 
15
  # Cache configuration - 12 hours
16
  CACHE_DURATION = 43200 # 12 hours
@@ -45,7 +40,7 @@ def get_cached_trek_xml():
45
  return None
46
 
47
  def get_product_price_and_link(product_name, variant=None):
48
- """Get price and link from Trek website XML using universal matcher"""
49
  try:
50
  # Get cached Trek XML
51
  xml_content = get_cached_trek_xml()
@@ -54,37 +49,88 @@ def get_product_price_and_link(product_name, variant=None):
54
 
55
  root = ET.fromstring(xml_content)
56
 
57
- # Convert XML to product list for matcher
58
- products = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  for item in root.findall('item'):
 
60
  rootlabel_elem = item.find('rootlabel')
61
  if rootlabel_elem is None or not rootlabel_elem.text:
62
  continue
 
 
 
 
63
 
64
- # Correct field names from XML
65
- price_elem = item.find('priceTaxWithCur')
66
- link_elem = item.find('productLink')
67
 
68
- products.append({
69
- 'name': rootlabel_elem.text,
70
- 'rootlabel': rootlabel_elem.text,
71
- 'price_elem': price_elem,
72
- 'link_elem': link_elem,
73
- 'xml_item': item
74
- })
75
-
76
- # Use universal matcher to find best match
77
- search_query = f"{product_name} {variant}".strip() if variant else product_name
78
- best_match = find_best_match(search_query, products, threshold=30)
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
- if best_match and best_match.get('price_elem') is not None:
81
- price_elem = best_match['price_elem']
82
- link_elem = best_match.get('link_elem')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
- if price_elem.text:
85
- price = float(price_elem.text.replace(',', '.'))
86
- link = link_elem.text if link_elem is not None else None
87
- return price, link
88
 
89
  return None, None
90
 
 
6
  import json
7
  import xml.etree.ElementTree as ET
8
  import time
 
 
 
 
 
9
 
10
  # Cache configuration - 12 hours
11
  CACHE_DURATION = 43200 # 12 hours
 
40
  return None
41
 
42
  def get_product_price_and_link(product_name, variant=None):
43
+ """Get price and link from Trek website XML"""
44
  try:
45
  # Get cached Trek XML
46
  xml_content = get_cached_trek_xml()
 
49
 
50
  root = ET.fromstring(xml_content)
51
 
52
+ # Normalize search terms
53
+ search_name = product_name.lower()
54
+ search_variant = variant.lower() if variant else ""
55
+
56
+ # Turkish character normalization
57
+ tr_map = {'ı': 'i', 'ğ': 'g', 'ü': 'u', 'ş': 's', 'ö': 'o', 'ç': 'c'}
58
+ for tr, en in tr_map.items():
59
+ search_name = search_name.replace(tr, en)
60
+ search_variant = search_variant.replace(tr, en)
61
+
62
+ best_match = None
63
+ best_score = 0
64
+
65
+ # Clean search name - remove year and parentheses
66
+ clean_search = re.sub(r'\s*\(\d{4}\)\s*', '', search_name).strip()
67
+
68
  for item in root.findall('item'):
69
+ # Get product name
70
  rootlabel_elem = item.find('rootlabel')
71
  if rootlabel_elem is None or not rootlabel_elem.text:
72
  continue
73
+
74
+ item_name = rootlabel_elem.text.lower()
75
+ for tr, en in tr_map.items():
76
+ item_name = item_name.replace(tr, en)
77
 
78
+ # Clean item name too
79
+ clean_item = re.sub(r'\s*\(\d{4}\)\s*', '', item_name).strip()
 
80
 
81
+ # Calculate match score with priority for exact matches
82
+ score = 0
83
+
84
+ # Exact match gets highest priority
85
+ if clean_search == clean_item:
86
+ score += 100
87
+ # Check if starts with exact product name (e.g., "fx 2" in "fx 2 kirmizi")
88
+ elif clean_item.startswith(clean_search + " ") or clean_item == clean_search:
89
+ score += 50
90
+ else:
91
+ # Partial matching
92
+ name_parts = clean_search.split()
93
+ for part in name_parts:
94
+ if part in clean_item:
95
+ score += 1
96
+
97
+ # Check variant if specified
98
+ if variant and search_variant in item_name:
99
+ score += 2 # Variant match is important
100
+
101
+ if score > best_score:
102
+ best_score = score
103
+ best_match = item
104
 
105
+ if best_match and best_score > 0:
106
+ # Extract price
107
+ price_elem = best_match.find('priceTaxWithCur')
108
+ price = price_elem.text if price_elem is not None and price_elem.text else None
109
+
110
+ # Round price
111
+ if price:
112
+ try:
113
+ price_float = float(price)
114
+ if price_float > 200000:
115
+ rounded = round(price_float / 5000) * 5000
116
+ price = f"{int(rounded):,}".replace(',', '.') + " TL"
117
+ elif price_float > 30000:
118
+ rounded = round(price_float / 1000) * 1000
119
+ price = f"{int(rounded):,}".replace(',', '.') + " TL"
120
+ elif price_float > 10000:
121
+ rounded = round(price_float / 100) * 100
122
+ price = f"{int(rounded):,}".replace(',', '.') + " TL"
123
+ else:
124
+ rounded = round(price_float / 10) * 10
125
+ price = f"{int(rounded):,}".replace(',', '.') + " TL"
126
+ except:
127
+ price = f"{price} TL"
128
+
129
+ # Extract link (field name is productLink, not productUrl!)
130
+ link_elem = best_match.find('productLink')
131
+ link = link_elem.text if link_elem is not None and link_elem.text else None
132
 
133
+ return price, link
 
 
 
134
 
135
  return None, None
136
 
smart_warehouse_with_price_ai.py DELETED
@@ -1,353 +0,0 @@
1
- """Smart warehouse with AI-powered product analysis"""
2
-
3
- import requests
4
- import re
5
- import os
6
- import json
7
- import xml.etree.ElementTree as ET
8
- import time
9
- from typing import List, Dict, Optional, Tuple
10
- from universal_product_matcher import find_best_match, normalize_text
11
- from ai_product_analyzer import analyze_complete_query, smart_product_search
12
-
13
- # Cache configuration - 12 hours
14
- CACHE_DURATION = 43200 # 12 hours
15
- cache = {
16
- 'warehouse_xml': {'data': None, 'time': 0},
17
- 'trek_xml': {'data': None, 'time': 0},
18
- 'products_summary': {'data': None, 'time': 0},
19
- 'search_results': {} # Cache for specific searches
20
- }
21
-
22
- def get_cached_trek_xml():
23
- """Get Trek XML with 12-hour caching"""
24
- current_time = time.time()
25
-
26
- if cache['trek_xml']['data'] and (current_time - cache['trek_xml']['time'] < CACHE_DURATION):
27
- print("🚴 Using cached Trek XML (12-hour cache)")
28
- return cache['trek_xml']['data']
29
-
30
- print("📡 Fetching fresh Trek XML...")
31
- try:
32
- url = 'https://www.trekbisiklet.com.tr/output/8582384479'
33
- response = requests.get(url, verify=False, timeout=10)
34
-
35
- if response.status_code == 200:
36
- cache['trek_xml']['data'] = response.content
37
- cache['trek_xml']['time'] = current_time
38
- return response.content
39
- else:
40
- return None
41
- except Exception as e:
42
- print(f"Trek XML fetch error: {e}")
43
- return None
44
-
45
- def get_cached_warehouse_xml():
46
- """Get warehouse XML with 12-hour caching"""
47
- current_time = time.time()
48
-
49
- if cache['warehouse_xml']['data'] and (current_time - cache['warehouse_xml']['time'] < CACHE_DURATION):
50
- print("📦 Using cached warehouse XML (12-hour cache)")
51
- return cache['warehouse_xml']['data']
52
-
53
- print("🔄 Fetching fresh warehouse XML...")
54
- try:
55
- url = 'https://veloconnect.wardin.com/v1/wardin?auth_token=97e0949096e88dd936f1bc2f0ffd3a07&auth_source=alatin.com.tr'
56
- response = requests.get(url, verify=False, timeout=10)
57
-
58
- if response.status_code == 200:
59
- cache['warehouse_xml']['data'] = response.text
60
- cache['warehouse_xml']['time'] = current_time
61
- return response.text
62
- else:
63
- return None
64
- except Exception as e:
65
- print(f"Warehouse XML fetch error: {e}")
66
- return None
67
-
68
- def get_all_products_with_details():
69
- """Get all products with details from warehouse XML"""
70
-
71
- # Check cache first
72
- current_time = time.time()
73
- if cache['products_summary']['data'] and (current_time - cache['products_summary']['time'] < CACHE_DURATION):
74
- print("✅ Using cached products summary")
75
- return cache['products_summary']['data']
76
-
77
- xml_text = get_cached_warehouse_xml()
78
- if not xml_text:
79
- return []
80
-
81
- # Parse products
82
- product_pattern = r'<Product>(.*?)</Product>'
83
- all_products = re.findall(product_pattern, xml_text, re.DOTALL)
84
-
85
- products_list = []
86
- for i, product_block in enumerate(all_products):
87
- # Extract product details
88
- name_match = re.search(r'<ProductName><!\[CDATA\[(.*?)\]\]></ProductName>', product_block)
89
- variant_match = re.search(r'<ProductVariant><!\[CDATA\[(.*?)\]\]></ProductVariant>', product_block)
90
- sku_match = re.search(r'<ProductSKU><!\[CDATA\[(.*?)\]\]></ProductSKU>', product_block)
91
-
92
- if not name_match:
93
- continue
94
-
95
- product_name = name_match.group(1)
96
- variant = variant_match.group(1) if variant_match else ""
97
- sku = sku_match.group(1) if sku_match else ""
98
-
99
- # Get warehouses with stock
100
- warehouses_with_stock = []
101
- warehouse_regex = r'<Warehouse>.*?<Name><!\[CDATA\[(.*?)\]\]></Name>.*?<Stock>(.*?)</Stock>.*?</Warehouse>'
102
- warehouses = re.findall(warehouse_regex, product_block, re.DOTALL)
103
-
104
- total_stock = 0
105
- for wh_name, wh_stock in warehouses:
106
- try:
107
- stock_count = int(wh_stock.strip())
108
- if stock_count > 0:
109
- warehouses_with_stock.append({
110
- "name": wh_name,
111
- "stock": stock_count
112
- })
113
- total_stock += stock_count
114
- except:
115
- pass
116
-
117
- # Determine category
118
- category = "genel"
119
- name_lower = product_name.lower()
120
- if "marlin" in name_lower or "roscoe" in name_lower or "fuel" in name_lower or "procaliber" in name_lower:
121
- category = "dağ"
122
- elif "domane" in name_lower or "émonda" in name_lower or "emonda" in name_lower or "madone" in name_lower:
123
- category = "yol"
124
- elif "fx" in name_lower or "ds" in name_lower or "verve" in name_lower:
125
- category = "şehir"
126
- elif "rail" in name_lower or "powerfly" in name_lower or "+" in name_lower:
127
- category = "elektrikli"
128
- elif "checkpoint" in name_lower:
129
- category = "gravel"
130
- elif "gobik" in name_lower:
131
- category = "giyim"
132
- elif "bontrager" in name_lower:
133
- category = "aksesuar"
134
-
135
- # Get price from Trek XML
136
- price, link = get_product_price_and_link(product_name, variant)
137
-
138
- product_info = {
139
- "index": i,
140
- "name": product_name,
141
- "variant": variant,
142
- "sku": sku,
143
- "category": category,
144
- "warehouses": warehouses_with_stock,
145
- "total_stock": total_stock,
146
- "price": price,
147
- "link": link,
148
- "full_name": f"{product_name} {variant}".strip()
149
- }
150
-
151
- products_list.append(product_info)
152
-
153
- # Cache the results
154
- cache['products_summary']['data'] = products_list
155
- cache['products_summary']['time'] = current_time
156
-
157
- return products_list
158
-
159
- def get_product_price_and_link(product_name, variant=None):
160
- """Get price and link from Trek website XML using universal matcher"""
161
- try:
162
- xml_content = get_cached_trek_xml()
163
- if not xml_content:
164
- return None, None
165
-
166
- root = ET.fromstring(xml_content)
167
-
168
- # Convert XML to product list for universal matcher
169
- products = []
170
- for item in root.findall('.//item'):
171
- title = item.find('rootlabel')
172
- if title is not None and title.text:
173
- # Correct field names from XML
174
- price_elem = item.find('priceTaxWithCur')
175
- link_elem = item.find('productLink')
176
-
177
- products.append({
178
- 'name': title.text,
179
- 'rootlabel': title.text,
180
- 'price_elem': price_elem,
181
- 'link_elem': link_elem
182
- })
183
-
184
- # Use universal matcher
185
- search_query = f"{product_name} {variant}".strip() if variant else product_name
186
- best_match = find_best_match(search_query, products, threshold=30)
187
-
188
- if best_match and best_match.get('price_elem') is not None:
189
- price_elem = best_match['price_elem']
190
- link_elem = best_match.get('link_elem')
191
-
192
- if price_elem.text:
193
- price = float(price_elem.text.replace(',', '.'))
194
- link = link_elem.text if link_elem is not None else None
195
- return price, link
196
-
197
- return None, None
198
- except Exception as e:
199
- print(f"Price lookup error: {e}")
200
- return None, None
201
-
202
- def search_products_smart_ai(user_message: str, context: Optional[Dict] = None) -> List[str]:
203
- """
204
- AI-powered product search
205
-
206
- Args:
207
- user_message: User's query
208
- context: Conversation context
209
-
210
- Returns:
211
- List of formatted product descriptions
212
- """
213
-
214
- # Check cache first
215
- current_time = time.time()
216
- cache_key = f"ai_search_{user_message.lower()}"
217
-
218
- if cache_key in cache['search_results']:
219
- cached = cache['search_results'][cache_key]
220
- if current_time - cached['time'] < CACHE_DURATION:
221
- print(f"✅ Using cached AI result for '{user_message}'")
222
- return cached['data']
223
-
224
- # Get all products
225
- all_products = get_all_products_with_details()
226
- if not all_products:
227
- return ["Ürün veritabanına erişilemiyor"]
228
-
229
- # AI analysis
230
- result = analyze_complete_query(user_message, all_products, context)
231
-
232
- # Format response based on AI analysis
233
- formatted_results = []
234
-
235
- if result["action"] == "greet":
236
- # Selamlama tespit edildi, ürün araması yapma
237
- return []
238
-
239
- elif result["action"] == "show_products" and result["products"]:
240
- # Ürünler bulundu
241
- for product in result["products"][:10]: # Max 10 ürün göster
242
- # Format product information
243
- name = product.get("name", "")
244
- variant = product.get("variant", "")
245
- full_name = f"{name} {variant}".strip() if variant else name
246
-
247
- # Price info
248
- price = product.get("price")
249
- price_str = f"{price:,.0f} TL" if price else "Fiyat bilgisi yok"
250
-
251
- # Stock info
252
- warehouses = product.get("warehouses", [])
253
- if warehouses:
254
- stock_info = []
255
- for wh in warehouses:
256
- stock_info.append(f"{wh['name']}: {wh['stock']} adet")
257
- stock_str = " | ".join(stock_info)
258
- else:
259
- stock_str = "Stok yok"
260
-
261
- # Link
262
- link = product.get("link", "")
263
-
264
- # Format result
265
- result_text = f"🚲 **{full_name}**\n"
266
- result_text += f"💰 Fiyat: {price_str}\n"
267
- result_text += f"📦 Stok: {stock_str}"
268
-
269
- if link:
270
- result_text += f"\n🔗 Link: {link}"
271
-
272
- formatted_results.append(result_text)
273
-
274
- elif result["action"] == "no_products":
275
- # Ürün bulunamadı
276
- analysis = result.get("analysis", {})
277
- if analysis.get("product_name"):
278
- formatted_results.append(f"'{analysis['product_name']}' ürünü bulunamadı")
279
- else:
280
- formatted_results.append("Aradığınız kriterlere uygun ürün bulunamadı")
281
-
282
- # Cache the results
283
- if formatted_results:
284
- cache['search_results'][cache_key] = {
285
- 'data': formatted_results,
286
- 'time': current_time
287
- }
288
-
289
- return formatted_results if formatted_results else ["Ürün bulunamadı"]
290
-
291
- def format_stock_message(user_message: str, context: Optional[Dict] = None) -> str:
292
- """
293
- Format stock message using AI-powered search
294
- """
295
-
296
- results = search_products_smart_ai(user_message, context)
297
-
298
- if not results:
299
- return ""
300
-
301
- # Combine results
302
- if len(results) == 1 and "bulunamadı" in results[0]:
303
- return results[0]
304
-
305
- # Format multiple results
306
- message = "🔍 **MAĞAZA STOK BİLGİSİ**\n\n"
307
- for i, result in enumerate(results, 1):
308
- if i > 1:
309
- message += "\n---\n\n"
310
- message += result
311
-
312
- return message
313
-
314
- # Test function
315
- def test_ai_search():
316
- """Test AI-powered search"""
317
-
318
- test_queries = [
319
- "FX 2 var mı?",
320
- "Marlin 5'in fiyatı nedir?",
321
- "50 bin altı dağ bisikleti",
322
- "elektrikli bisiklet var mı?",
323
- "merhaba",
324
- "süper",
325
- "100-200 bin arası yol bisikleti",
326
- "GOBIK forma var mı?",
327
- "caddebostan'da hangi bisikletler var?"
328
- ]
329
-
330
- print("\n" + "="*60)
331
- print("AI-POWERED SEARCH TEST")
332
- print("="*60)
333
-
334
- for query in test_queries:
335
- print(f"\n📝 Query: \"{query}\"")
336
- results = search_products_smart_ai(query)
337
-
338
- if results:
339
- print(f"✅ Found {len(results)} result(s):")
340
- for result in results[:3]: # Show max 3
341
- # Truncate long results for display
342
- lines = result.split('\n')
343
- for line in lines[:3]: # Show first 3 lines
344
- print(f" {line}")
345
- if len(lines) > 3:
346
- print(" ...")
347
- else:
348
- print("❌ No results")
349
-
350
- print("\n" + "="*60)
351
-
352
- if __name__ == "__main__":
353
- test_ai_search()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
test_marlin_price.py DELETED
@@ -1,126 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Test Marlin 5 price lookup"""
3
-
4
- import os
5
- os.environ['OPENAI_API_KEY'] = os.getenv('OPENAI_API_KEY', '')
6
-
7
- from smart_warehouse_with_price import get_product_price_and_link, get_warehouse_stock_smart_with_price
8
- from universal_product_matcher import find_best_match
9
- import xml.etree.ElementTree as ET
10
- import requests
11
-
12
- def test_direct_xml():
13
- """Trek XML'den direkt Marlin 5 ara"""
14
- print("\n=== DIRECT XML TEST ===")
15
-
16
- try:
17
- # Get Trek XML
18
- url = 'https://www.trekbisiklet.com.tr/output/8582384479'
19
- response = requests.get(url, verify=False, timeout=10)
20
-
21
- if response.status_code == 200:
22
- root = ET.fromstring(response.content)
23
-
24
- # Search for Marlin in XML
25
- marlin_found = []
26
- for item in root.findall('.//item'):
27
- title_elem = item.find('rootlabel')
28
- if title_elem is not None and title_elem.text:
29
- title = title_elem.text
30
- if 'marlin' in title.lower() and '5' in title:
31
- price_elem = item.find('fiyat')
32
- link_elem = item.find('link')
33
- marlin_found.append({
34
- 'title': title,
35
- 'price': price_elem.text if price_elem is not None else 'N/A',
36
- 'link': link_elem.text if link_elem is not None else 'N/A'
37
- })
38
-
39
- print(f"Found {len(marlin_found)} Marlin 5 products in XML:")
40
- for m in marlin_found[:5]: # Show first 5
41
- print(f" - {m['title']}")
42
- print(f" Price: {m['price']}")
43
- print(f" Link: {m['link'][:50]}..." if m['link'] != 'N/A' else " Link: N/A")
44
- else:
45
- print(f"Failed to fetch XML: {response.status_code}")
46
-
47
- except Exception as e:
48
- print(f"Error: {e}")
49
-
50
- def test_price_function():
51
- """Test get_product_price_and_link function"""
52
- print("\n=== PRICE FUNCTION TEST ===")
53
-
54
- test_products = [
55
- "Marlin 5",
56
- "Marlin 5 (2026)",
57
- "marlin 5",
58
- "MARLIN 5"
59
- ]
60
-
61
- for product in test_products:
62
- price, link = get_product_price_and_link(product)
63
- print(f"\nProduct: '{product}'")
64
- print(f" Price: {price}")
65
- print(f" Link: {link[:50] if link else 'None'}...")
66
-
67
- def test_warehouse_search():
68
- """Test full warehouse search"""
69
- print("\n=== WAREHOUSE SEARCH TEST ===")
70
-
71
- result = get_warehouse_stock_smart_with_price("Marlin 5")
72
- print(f"Search result for 'Marlin 5':")
73
- if result:
74
- for item in result[:2]: # Show first 2
75
- print(f"\n{item}")
76
- else:
77
- print("No results found")
78
-
79
- def test_universal_matcher():
80
- """Test universal matcher with Trek XML"""
81
- print("\n=== UNIVERSAL MATCHER TEST ===")
82
-
83
- try:
84
- # Get Trek XML
85
- url = 'https://www.trekbisiklet.com.tr/output/8582384479'
86
- response = requests.get(url, verify=False, timeout=10)
87
-
88
- if response.status_code == 200:
89
- root = ET.fromstring(response.content)
90
-
91
- # Convert to product list
92
- products = []
93
- for item in root.findall('.//item'):
94
- title_elem = item.find('rootlabel')
95
- if title_elem is not None and title_elem.text:
96
- products.append({
97
- 'name': title_elem.text,
98
- 'rootlabel': title_elem.text
99
- })
100
-
101
- print(f"Total products in XML: {len(products)}")
102
-
103
- # Test matcher
104
- result = find_best_match("Marlin 5", products, threshold=20)
105
- if result:
106
- print(f"\nBest match for 'Marlin 5':")
107
- print(f" Name: {result['name']}")
108
- print(f" Score: {result.get('match_score', 0)}")
109
- else:
110
- print("No match found")
111
-
112
- # Show all Marlin products
113
- print("\nAll Marlin products in XML:")
114
- marlin_products = [p for p in products if 'marlin' in p['name'].lower()]
115
- for p in marlin_products[:10]:
116
- print(f" - {p['name']}")
117
-
118
- except Exception as e:
119
- print(f"Error: {e}")
120
-
121
- if __name__ == "__main__":
122
- print("Testing Marlin 5 price lookup...")
123
- test_direct_xml()
124
- test_price_function()
125
- test_universal_matcher()
126
- test_warehouse_search()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
universal_product_matcher.py DELETED
@@ -1,241 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
-
4
- """
5
- Universal Product Matcher
6
- Genel ürün eşleştirme algoritması - marka/model bağımsız
7
- """
8
-
9
- import re
10
- from typing import List, Dict, Tuple, Optional
11
- import difflib
12
-
13
- def normalize_text(text: str) -> str:
14
- """Metni normalize et"""
15
- # Küçük harf
16
- text = text.lower()
17
-
18
- # Türkçe karakterleri dönüştür
19
- tr_map = {'ı': 'i', 'ğ': 'g', 'ü': 'u', 'ş': 's', 'ö': 'o', 'ç': 'c'}
20
- for tr, en in tr_map.items():
21
- text = text.replace(tr, en)
22
-
23
- # Yıl bilgisini kaldır (2024), (2025) vb.
24
- text = re.sub(r'\s*\(\d{4}\)\s*', '', text)
25
-
26
- # Fazla boşlukları temizle
27
- text = ' '.join(text.split())
28
-
29
- return text.strip()
30
-
31
- def extract_product_components(product_name: str) -> Dict:
32
- """
33
- Ürün adını bileşenlerine ayır
34
- Örnek: "Marlin 5 Kırmızı L" -> {brand: "marlin", model: "5", color: "kirmizi", size: "l"}
35
- """
36
- normalized = normalize_text(product_name)
37
-
38
- # Sayıları bul (model numaraları için)
39
- numbers = re.findall(r'\b\d+\b', normalized)
40
-
41
- # Renkleri bul
42
- colors = ['kirmizi', 'mavi', 'siyah', 'beyaz', 'gri', 'yesil', 'turuncu', 'mor', 'sari', 'pembe']
43
- found_colors = [c for c in colors if c in normalized]
44
-
45
- # Bedenleri bul
46
- sizes = ['xxs', 'xs', 's', 'm', 'ml', 'l', 'xl', 'xxl', 'small', 'medium', 'large']
47
- found_sizes = [s for s in sizes if f' {s} ' in f' {normalized} ' or normalized.endswith(f' {s}')]
48
-
49
- # Ana kelimeler (brand/model adı - sayı, renk ve beden hariç)
50
- words = normalized.split()
51
- main_words = []
52
- for word in words:
53
- if (word not in numbers and
54
- word not in found_colors and
55
- word not in found_sizes and
56
- not word.isdigit()):
57
- main_words.append(word)
58
-
59
- return {
60
- 'normalized': normalized,
61
- 'main_words': main_words,
62
- 'numbers': numbers,
63
- 'colors': found_colors,
64
- 'sizes': found_sizes,
65
- 'word_count': len(words)
66
- }
67
-
68
- def calculate_match_score(search_components: Dict, item_components: Dict) -> float:
69
- """
70
- İki ürün arasındaki eşleşme skorunu hesapla
71
- Genel algoritma - marka bağımsız
72
- """
73
- score = 0.0
74
-
75
- # 1. Ana kelimeler eşleşmesi (brand/model adı)
76
- if search_components['main_words'] and item_components['main_words']:
77
- # Tüm ana kelimelerin eşleşmesi gerekiyor
78
- search_main = set(search_components['main_words'])
79
- item_main = set(item_components['main_words'])
80
-
81
- # Aranan tüm ana kelimeler üründe var mı?
82
- if search_main.issubset(item_main):
83
- # Tam eşleşme bonus
84
- if search_main == item_main:
85
- score += 50
86
- else:
87
- score += 30
88
- else:
89
- # Ana kelimeler eşleşmiyorsa, bu ürün değil
90
- return 0
91
-
92
- # 2. Model numarası eşleşmesi (kritik!)
93
- if search_components['numbers']:
94
- search_nums = set(search_components['numbers'])
95
- item_nums = set(item_components['numbers'])
96
-
97
- if search_nums:
98
- if search_nums == item_nums:
99
- score += 40 # Tam numara eşleşmesi
100
- elif search_nums.issubset(item_nums):
101
- score += 20 # Kısmi numara eşleşmesi
102
- else:
103
- # Aranan numara yoksa, büyük ihtimalle yanlış ürün
104
- score *= 0.5
105
-
106
- # 3. String benzerliği (Levenshtein)
107
- similarity = difflib.SequenceMatcher(
108
- None,
109
- search_components['normalized'],
110
- item_components['normalized']
111
- ).ratio()
112
- score += similarity * 20
113
-
114
- # 4. Kelime sırası bonusu
115
- search_start = ' '.join(search_components['normalized'].split()[:2])
116
- item_start = ' '.join(item_components['normalized'].split()[:2])
117
- if search_start == item_start:
118
- score += 15
119
-
120
- # 5. Renk eşleşmesi (opsiyonel)
121
- if search_components['colors'] and item_components['colors']:
122
- if set(search_components['colors']) == set(item_components['colors']):
123
- score += 5
124
-
125
- # 6. Beden eşleşmesi (opsiyonel)
126
- if search_components['sizes'] and item_components['sizes']:
127
- if set(search_components['sizes']) == set(item_components['sizes']):
128
- score += 5
129
-
130
- return score
131
-
132
- def find_best_match(search_query: str, products: List[Dict], threshold: float = 30) -> Optional[Dict]:
133
- """
134
- En iyi eşleşen ürünü bul
135
-
136
- Args:
137
- search_query: Aranan ürün
138
- products: Ürün listesi [{name: str, ...}]
139
- threshold: Minimum eşleşme skoru
140
-
141
- Returns:
142
- En iyi eşleşen ürün veya None
143
- """
144
- search_components = extract_product_components(search_query)
145
-
146
- best_match = None
147
- best_score = 0
148
-
149
- for product in products:
150
- product_name = product.get('name', '') or product.get('rootlabel', '')
151
- if not product_name:
152
- continue
153
-
154
- item_components = extract_product_components(product_name)
155
- score = calculate_match_score(search_components, item_components)
156
-
157
- if score > best_score and score >= threshold:
158
- best_score = score
159
- best_match = product
160
- best_match['match_score'] = score
161
-
162
- return best_match
163
-
164
- def find_all_matches(search_query: str, products: List[Dict], threshold: float = 20, max_results: int = 10) -> List[Dict]:
165
- """
166
- Eşleşen tüm ürünleri bul ve skorla sırala
167
- """
168
- search_components = extract_product_components(search_query)
169
-
170
- matches = []
171
-
172
- for product in products:
173
- product_name = product.get('name', '') or product.get('rootlabel', '')
174
- if not product_name:
175
- continue
176
-
177
- item_components = extract_product_components(product_name)
178
- score = calculate_match_score(search_components, item_components)
179
-
180
- if score >= threshold:
181
- product_copy = product.copy()
182
- product_copy['match_score'] = score
183
- matches.append(product_copy)
184
-
185
- # Skora göre sırala
186
- matches.sort(key=lambda x: x['match_score'], reverse=True)
187
-
188
- return matches[:max_results]
189
-
190
- # Test fonksiyonu
191
- def test_matcher():
192
- """Test senaryoları"""
193
-
194
- # Örnek ürünler
195
- test_products = [
196
- {"name": "Marlin 5 (2026)", "price": 53000},
197
- {"name": "Marlin 7", "price": 72000},
198
- {"name": "Rail 9.9 XX AXS T-Type Gen 5", "price": 700000},
199
- {"name": "FX 2", "price": 65000},
200
- {"name": "FX Sport 6", "price": 183000},
201
- {"name": "Domane AL 2", "price": 110000},
202
- {"name": "Marlin 5 Kırmızı L", "price": 53000},
203
- {"name": "FX 2 Disc Mavi M", "price": 65000},
204
- ]
205
-
206
- # Test sorguları
207
- test_queries = [
208
- "Marlin 5",
209
- "FX 2",
210
- "Rail 9",
211
- "Domane",
212
- "FX Sport",
213
- "Marlin"
214
- ]
215
-
216
- print("\n" + "="*60)
217
- print("UNIVERSAL PRODUCT MATCHER TEST")
218
- print("="*60)
219
-
220
- for query in test_queries:
221
- print(f"\n🔍 Aranan: '{query}'")
222
- best = find_best_match(query, test_products)
223
-
224
- if best:
225
- print(f"✅ En İyi Eşleşme: {best['name']}")
226
- print(f" Skor: {best['match_score']:.1f}")
227
- print(f" Fiyat: {best['price']:,} TL")
228
- else:
229
- print("❌ Eşleşme bulunamadı")
230
-
231
- # Tüm eşleşmeleri göster
232
- all_matches = find_all_matches(query, test_products, threshold=10)
233
- if len(all_matches) > 1:
234
- print(" Diğer eşleşmeler:")
235
- for m in all_matches[1:3]: # İlk 3
236
- print(f" - {m['name']} (Skor: {m['match_score']:.1f})")
237
-
238
- print("\n" + "="*60)
239
-
240
- if __name__ == "__main__":
241
- test_matcher()