Revert to backup - removing AI modifications
Browse files- __pycache__/smart_warehouse_with_price.cpython-312.pyc +0 -0
- ai_product_analyzer.py +0 -328
- app.py +7 -22
- debug_xml_fields.py +0 -59
- prompts.py +1 -1
- smart_warehouse_with_price.py +75 -29
- smart_warehouse_with_price_ai.py +0 -353
- test_marlin_price.py +0 -126
- universal_product_matcher.py +0 -241
__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 -
|
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 |
-
|
41 |
-
logger.info("✅ AI-powered smart warehouse loaded")
|
42 |
except ImportError:
|
43 |
-
|
44 |
-
|
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
|
120 |
-
"""B2B API'den mağaza stok bilgilerini çek -
|
121 |
-
# Try
|
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🚨🚨🚨
|
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
|
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 |
-
#
|
58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
#
|
65 |
-
|
66 |
-
link_elem = item.find('productLink')
|
67 |
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
79 |
|
80 |
-
if best_match and
|
81 |
-
|
82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
83 |
|
84 |
-
|
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()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|