SamiKoen commited on
Commit
179b8e3
·
1 Parent(s): 607111a

Update smart_warehouse_with_price.py - sync with WhatsApp version for better product matching

Browse files
Files changed (1) hide show
  1. smart_warehouse_with_price.py +333 -51
smart_warehouse_with_price.py CHANGED
@@ -7,8 +7,8 @@ import json
7
  import xml.etree.ElementTree as ET
8
  import time
9
 
10
- # Cache configuration - 12 hours
11
- CACHE_DURATION = 43200 # 12 hours
12
  cache = {
13
  'warehouse_xml': {'data': None, 'time': 0},
14
  'trek_xml': {'data': None, 'time': 0},
@@ -21,10 +21,9 @@ def get_cached_trek_xml():
21
  current_time = time.time()
22
 
23
  if cache['trek_xml']['data'] and (current_time - cache['trek_xml']['time'] < CACHE_DURATION):
24
- print("🚴 Using cached Trek XML (12-hour cache)")
25
  return cache['trek_xml']['data']
26
 
27
- print("📡 Fetching fresh Trek XML...")
28
  try:
29
  url = 'https://www.trekbisiklet.com.tr/output/8582384479'
30
  response = requests.get(url, verify=False, timeout=10)
@@ -36,9 +35,139 @@ def get_cached_trek_xml():
36
  else:
37
  return None
38
  except Exception as e:
39
- print(f"Trek XML fetch error: {e}")
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:
@@ -49,19 +178,33 @@ def get_product_price_and_link(product_name, variant=None):
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
  for item in root.findall('item'):
66
  # Get product name
67
  rootlabel_elem = item.find('rootlabel')
@@ -72,12 +215,24 @@ def get_product_price_and_link(product_name, variant=None):
72
  for tr, en in tr_map.items():
73
  item_name = item_name.replace(tr, en)
74
 
75
- # Calculate match score
 
 
 
76
  score = 0
77
- name_parts = search_name.split()
78
- for part in name_parts:
79
- if part in item_name:
80
- score += 1
 
 
 
 
 
 
 
 
 
81
 
82
  # Check variant if specified
83
  if variant and search_variant in item_name:
@@ -120,7 +275,6 @@ def get_product_price_and_link(product_name, variant=None):
120
  return None, None
121
 
122
  except Exception as e:
123
- print(f"Error getting price/link: {e}")
124
  return None, None
125
 
126
  def get_cached_warehouse_xml():
@@ -128,28 +282,23 @@ def get_cached_warehouse_xml():
128
  current_time = time.time()
129
 
130
  if cache['warehouse_xml']['data'] and (current_time - cache['warehouse_xml']['time'] < CACHE_DURATION):
131
- print("📦 Using cached warehouse XML (12-hour cache)")
132
  return cache['warehouse_xml']['data']
133
 
134
- print("📡 Fetching fresh warehouse XML...")
135
  for attempt in range(3):
136
  try:
137
  url = 'https://video.trek-turkey.com/bizimhesap-warehouse-xml-b2b-api-v2.php'
138
  timeout_val = 10 + (attempt * 5)
139
  response = requests.get(url, verify=False, timeout=timeout_val)
140
  xml_text = response.text
141
- print(f"DEBUG - XML fetched: {len(xml_text)} characters (attempt {attempt+1})")
142
-
143
  cache['warehouse_xml']['data'] = xml_text
144
  cache['warehouse_xml']['time'] = current_time
145
 
146
  return xml_text
147
  except requests.exceptions.Timeout:
148
- print(f"XML fetch timeout (attempt {attempt+1}/3, timeout={timeout_val}s)")
149
  if attempt == 2:
150
  return None
151
  except Exception as e:
152
- print(f"XML fetch error: {e}")
153
  return None
154
 
155
  return None
@@ -180,14 +329,24 @@ def get_warehouse_stock_smart_with_price(user_message, previous_result=None):
180
  'kesinlikle', 'elbette', 'tabii', 'tabiki', 'doğru', 'yanlış'
181
  ]
182
 
 
183
  if clean_message in non_product_words:
184
  return None
185
-
186
- # Kısa tek kelimeler genellikle ürün adı değil
187
- if len(clean_message.split()) == 1 and len(clean_message) < 5:
 
 
 
 
 
 
 
 
188
  return None
189
 
190
- # Soru kontrolü - bunlar ürün araması değil
 
191
  question_indicators = [
192
  'musun', 'müsün', 'misin', 'mısın', 'miyim', 'mıyım',
193
  'musunuz', 'müsünüz', 'misiniz', 'mısınız',
@@ -195,31 +354,48 @@ def get_warehouse_stock_smart_with_price(user_message, previous_result=None):
195
  'ulaşamıyor', 'yapamıyor', 'gönderemiyor', 'edemiyor',
196
  '?'
197
  ]
 
 
 
 
 
 
 
198
 
199
- for indicator in question_indicators:
200
- if indicator in clean_message:
201
- return None
 
 
 
 
 
202
 
203
  # Check search cache first
204
- cache_key = user_message.lower()
205
  current_time = time.time()
 
206
  if cache_key in cache['search_results']:
207
  cached = cache['search_results'][cache_key]
208
  if current_time - cached['time'] < CACHE_DURATION:
209
- print(f"✅ Using cached result for '{user_message}' (12-hour cache)")
210
  return cached['data']
 
 
211
 
212
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
213
 
214
  # Check if user is asking about specific warehouse
215
  warehouse_keywords = {
216
  'caddebostan': 'Caddebostan',
217
- 'ortaköy': 'Ortaköy',
218
  'ortakoy': 'Ortaköy',
219
  'alsancak': 'Alsancak',
220
  'izmir': 'Alsancak',
221
  'bahçeköy': 'Bahçeköy',
222
- 'bahcekoy': 'Bahçeköy'
 
 
223
  }
224
 
225
  user_lower = user_message.lower()
@@ -269,7 +445,35 @@ def get_warehouse_stock_smart_with_price(user_message, previous_result=None):
269
  if asked_warehouse:
270
  warehouse_filter = f"\nIMPORTANT: User is asking specifically about {asked_warehouse} warehouse. Only return products available in that warehouse."
271
 
272
- # GPT prompt with enhanced instructions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  smart_prompt = f"""User is asking: "{user_message}"
274
 
275
  FIRST CHECK: Is this actually a product search?
@@ -309,19 +513,42 @@ Examples of correct responses:
309
  - "45" (single product found)
310
  - "-1" (no products found)"""
311
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  headers = {
313
  "Content-Type": "application/json",
314
  "Authorization": f"Bearer {OPENAI_API_KEY}"
315
  }
316
 
 
317
  payload = {
318
  "model": "gpt-5.2-chat-latest",
319
  "messages": [
320
  {"role": "system", "content": "You are a product matcher. Find ALL matching products. Return only index numbers."},
321
  {"role": "user", "content": smart_prompt}
322
- ],
323
-
324
- "max_tokens": 100
325
  }
326
 
327
  try:
@@ -336,11 +563,31 @@ Examples of correct responses:
336
  result = response.json()
337
  indices_str = result['choices'][0]['message']['content'].strip()
338
 
339
- print(f"DEBUG - GPT response: '{indices_str}'")
340
-
341
- # Handle empty response
342
  if not indices_str or indices_str == "-1":
343
- return None # GPT'ye bırak, "Ürün bulunamadı" deme
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
 
345
  try:
346
  # Filter out empty strings and parse indices
@@ -361,13 +608,33 @@ Examples of correct responses:
361
  # Get product details
362
  name_match = re.search(r'<ProductName><!\[CDATA\[(.*?)\]\]></ProductName>', product_block)
363
  variant_match = re.search(r'<ProductVariant><!\[CDATA\[(.*?)\]\]></ProductVariant>', product_block)
 
364
 
365
  if name_match:
366
  product_name = name_match.group(1)
367
  variant = variant_match.group(1) if variant_match else ""
368
 
369
- # Get price and link from Trek website
370
- price, link = get_product_price_and_link(product_name, variant)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
 
372
  variant_info = {
373
  'name': product_name,
@@ -451,26 +718,41 @@ Examples of correct responses:
451
  result.append(f"• {v['variant']}: {warehouses_str}")
452
 
453
  else:
454
- return None # Stok yoksa GPT'ye bırak
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
455
 
456
  # Cache the result before returning
457
  cache['search_results'][cache_key] = {
458
  'data': result,
459
  'time': current_time
460
  }
461
- print(f"💾 Cached result for '{user_message}' (12-hour cache)")
462
-
463
  return result
464
 
465
  except (ValueError, IndexError) as e:
466
- print(f"DEBUG - Error parsing indices: {e}")
467
  return None
468
  else:
469
- print(f"GPT API error: {response.status_code}")
470
  return None
471
 
472
  except Exception as e:
473
- print(f"Error calling GPT: {e}")
474
  return None
475
 
476
  def format_warehouse_name(wh_name):
 
7
  import xml.etree.ElementTree as ET
8
  import time
9
 
10
+ # Cache configuration - 2 hours (reduced from 12 hours for more accurate results)
11
+ CACHE_DURATION = 7200 # 2 hours
12
  cache = {
13
  'warehouse_xml': {'data': None, 'time': 0},
14
  'trek_xml': {'data': None, 'time': 0},
 
21
  current_time = time.time()
22
 
23
  if cache['trek_xml']['data'] and (current_time - cache['trek_xml']['time'] < CACHE_DURATION):
24
+ cache_age = (current_time - cache['trek_xml']['time']) / 60 # in minutes
25
  return cache['trek_xml']['data']
26
 
 
27
  try:
28
  url = 'https://www.trekbisiklet.com.tr/output/8582384479'
29
  response = requests.get(url, verify=False, timeout=10)
 
35
  else:
36
  return None
37
  except Exception as e:
 
38
  return None
39
 
40
+ def apply_price_rounding(price_str):
41
+ """Apply the same price rounding formula used in app.py"""
42
+ if not price_str:
43
+ return price_str
44
+
45
+ try:
46
+ price_float = float(price_str)
47
+ if price_float > 200000:
48
+ return str(round(price_float / 5000) * 5000)
49
+ elif price_float > 30000:
50
+ return str(round(price_float / 1000) * 1000)
51
+ elif price_float > 10000:
52
+ return str(round(price_float / 100) * 100)
53
+ else:
54
+ return str(round(price_float / 10) * 10)
55
+ except:
56
+ return price_str
57
+
58
+ def get_product_price_and_link_by_sku(product_code):
59
+ """Get price and link from Trek XML using improved SKU matching with new XML fields
60
+ Uses: stockCode, rootProductStockCode, isOptionOfAProduct, isOptionedProduct
61
+ Level 1: Search variants by stockCode where isOptionOfAProduct=1
62
+ Level 2: Search main products by stockCode where isOptionOfAProduct=0
63
+ Level 3: Search by rootProductStockCode for variant-to-main mapping
64
+ """
65
+ try:
66
+ # Import XML parsing for cleaner approach
67
+ import xml.etree.ElementTree as ET
68
+
69
+ # Get cached Trek XML
70
+ xml_content = get_cached_trek_xml()
71
+ if not xml_content:
72
+ return None, None
73
+
74
+ # Convert bytes to string if needed
75
+ if isinstance(xml_content, bytes):
76
+ xml_content = xml_content.decode('utf-8')
77
+
78
+ # Parse XML properly instead of regex
79
+ try:
80
+ root = ET.fromstring(xml_content)
81
+ except:
82
+ # Fallback to regex if XML parsing fails
83
+ return get_product_price_and_link_by_sku_regex(product_code)
84
+
85
+ # Level 1: Search variants first (isOptionOfAProduct=1)
86
+ for item in root.findall('.//item'):
87
+ is_option_element = item.find('isOptionOfAProduct')
88
+ stock_code_element = item.find('stockCode')
89
+
90
+ if (is_option_element is not None and is_option_element.text == '1' and
91
+ stock_code_element is not None and stock_code_element.text and stock_code_element.text.strip() == product_code):
92
+
93
+ price_element = item.find('priceTaxWithCur')
94
+ link_element = item.find('productLink')
95
+
96
+ if price_element is not None and link_element is not None:
97
+ rounded_price = apply_price_rounding(price_element.text)
98
+ return rounded_price, link_element.text
99
+
100
+ # Level 2: Search main products (isOptionOfAProduct=0)
101
+ for item in root.findall('.//item'):
102
+ is_option_element = item.find('isOptionOfAProduct')
103
+ stock_code_element = item.find('stockCode')
104
+
105
+ if (is_option_element is not None and is_option_element.text == '0' and
106
+ stock_code_element is not None and stock_code_element.text and stock_code_element.text.strip() == product_code):
107
+
108
+ price_element = item.find('priceTaxWithCur')
109
+ link_element = item.find('productLink')
110
+
111
+ if price_element is not None and link_element is not None:
112
+ rounded_price = apply_price_rounding(price_element.text)
113
+ return rounded_price, link_element.text
114
+
115
+ # Level 3: Search by rootProductStockCode (variant parent lookup)
116
+ for item in root.findall('.//item'):
117
+ root_stock_element = item.find('rootProductStockCode')
118
+
119
+ if (root_stock_element is not None and root_stock_element.text and root_stock_element.text.strip() == product_code):
120
+ price_element = item.find('priceTaxWithCur')
121
+ link_element = item.find('productLink')
122
+
123
+ if price_element is not None and link_element is not None:
124
+ rounded_price = apply_price_rounding(price_element.text)
125
+ return rounded_price, link_element.text
126
+
127
+ # Not found
128
+ return None, None
129
+
130
+ except Exception as e:
131
+ return None, None
132
+
133
+ def get_product_price_and_link_by_sku_regex(product_code):
134
+ """Fallback regex method for SKU lookup if XML parsing fails"""
135
+ try:
136
+ xml_content = get_cached_trek_xml()
137
+ if isinstance(xml_content, bytes):
138
+ xml_content = xml_content.decode('utf-8')
139
+
140
+ # Level 1: Search in variants first (isOptionOfAProduct=1)
141
+ variant_pattern = rf'<isOptionOfAProduct>1</isOptionOfAProduct>.*?<stockCode><!\[CDATA\[{re.escape(product_code)}\]\]></stockCode>.*?(?=<item>|$)'
142
+ variant_match = re.search(variant_pattern, xml_content, re.DOTALL)
143
+
144
+ if variant_match:
145
+ section = variant_match.group(0)
146
+ price_match = re.search(r'<price><!\[CDATA\[(.*?)\]\]></price>', section)
147
+ link_match = re.search(r'<producturl><!\[CDATA\[(.*?)\]\]></producturl>', section)
148
+
149
+ if price_match and link_match:
150
+ rounded_price = apply_price_rounding(price_match.group(1))
151
+ return rounded_price, link_match.group(1)
152
+
153
+ # Level 2: Search in main products (isOptionOfAProduct=0)
154
+ main_pattern = rf'<isOptionOfAProduct>0</isOptionOfAProduct>.*?<stockCode><!\[CDATA\[{re.escape(product_code)}\]\]></stockCode>.*?(?=<item>|$)'
155
+ main_match = re.search(main_pattern, xml_content, re.DOTALL)
156
+
157
+ if main_match:
158
+ section = main_match.group(0)
159
+ price_match = re.search(r'<price><!\[CDATA\[(.*?)\]\]></price>', section)
160
+ link_match = re.search(r'<producturl><!\[CDATA\[(.*?)\]\]></producturl>', section)
161
+
162
+ if price_match and link_match:
163
+ rounded_price = apply_price_rounding(price_match.group(1))
164
+ return rounded_price, link_match.group(1)
165
+
166
+ return None, None
167
+
168
+ except Exception as e:
169
+ return None, None
170
+
171
  def get_product_price_and_link(product_name, variant=None):
172
  """Get price and link from Trek website XML"""
173
  try:
 
178
 
179
  root = ET.fromstring(xml_content)
180
 
181
+ # Turkish character normalization FIRST (before lower())
182
+ tr_map = {
183
+ 'İ': 'i', 'I': 'i', 'ı': 'i', # All I variations to i
184
+ 'Ğ': 'g', 'ğ': 'g',
185
+ 'Ü': 'u', 'ü': 'u',
186
+ 'Ş': 's', 'ş': 's',
187
+ 'Ö': 'o', 'ö': 'o',
188
+ 'Ç': 'c', 'ç': 'c'
189
+ }
190
 
191
+ # Apply normalization to original (before lower)
192
+ search_name_normalized = product_name
193
+ search_variant_normalized = variant if variant else ""
194
  for tr, en in tr_map.items():
195
+ search_name_normalized = search_name_normalized.replace(tr, en)
196
+ search_variant_normalized = search_variant_normalized.replace(tr, en)
197
+
198
+ # Now lowercase
199
+ search_name = search_name_normalized.lower()
200
+ search_variant = search_variant_normalized.lower()
201
 
202
  best_match = None
203
  best_score = 0
204
 
205
+ # Clean search name - remove year and parentheses
206
+ clean_search = re.sub(r'\s*\(\d{4}\)\s*', '', search_name).strip()
207
+
208
  for item in root.findall('item'):
209
  # Get product name
210
  rootlabel_elem = item.find('rootlabel')
 
215
  for tr, en in tr_map.items():
216
  item_name = item_name.replace(tr, en)
217
 
218
+ # Clean item name too
219
+ clean_item = re.sub(r'\s*\(\d{4}\)\s*', '', item_name).strip()
220
+
221
+ # Calculate match score with priority for exact matches
222
  score = 0
223
+
224
+ # Exact match gets highest priority
225
+ if clean_search == clean_item:
226
+ score += 100
227
+ # Check if starts with exact product name (e.g., "fx 2" in "fx 2 kirmizi")
228
+ elif clean_item.startswith(clean_search + " ") or clean_item == clean_search:
229
+ score += 50
230
+ else:
231
+ # Partial matching
232
+ name_parts = clean_search.split()
233
+ for part in name_parts:
234
+ if part in clean_item:
235
+ score += 1
236
 
237
  # Check variant if specified
238
  if variant and search_variant in item_name:
 
275
  return None, None
276
 
277
  except Exception as e:
 
278
  return None, None
279
 
280
  def get_cached_warehouse_xml():
 
282
  current_time = time.time()
283
 
284
  if cache['warehouse_xml']['data'] and (current_time - cache['warehouse_xml']['time'] < CACHE_DURATION):
285
+ cache_age = (current_time - cache['warehouse_xml']['time']) / 60 # in minutes
286
  return cache['warehouse_xml']['data']
287
 
 
288
  for attempt in range(3):
289
  try:
290
  url = 'https://video.trek-turkey.com/bizimhesap-warehouse-xml-b2b-api-v2.php'
291
  timeout_val = 10 + (attempt * 5)
292
  response = requests.get(url, verify=False, timeout=timeout_val)
293
  xml_text = response.text
 
 
294
  cache['warehouse_xml']['data'] = xml_text
295
  cache['warehouse_xml']['time'] = current_time
296
 
297
  return xml_text
298
  except requests.exceptions.Timeout:
 
299
  if attempt == 2:
300
  return None
301
  except Exception as e:
 
302
  return None
303
 
304
  return None
 
329
  'kesinlikle', 'elbette', 'tabii', 'tabiki', 'doğru', 'yanlış'
330
  ]
331
 
332
+ # Check if message is just a simple response
333
  if clean_message in non_product_words:
334
  return None
335
+
336
+ # Brand keywords that should ALWAYS trigger product search regardless of length
337
+ brand_keywords = ['gobik', 'trek', 'bontrager', 'kask', 'shimano', 'sram', 'garmin', 'wahoo']
338
+
339
+ # Check if message contains a brand keyword
340
+ contains_brand = any(brand in clean_message for brand in brand_keywords)
341
+
342
+ # Check if it's a single word that's likely not a product
343
+ # BUT allow if it contains a known brand
344
+ if not contains_brand and len(clean_message.split()) == 1 and len(clean_message) < 5:
345
+ # Short single words are usually not product names
346
  return None
347
 
348
+ # Check if this is a question rather than a product search
349
+ # BUT skip this check if message contains a known brand
350
  question_indicators = [
351
  'musun', 'müsün', 'misin', 'mısın', 'miyim', 'mıyım',
352
  'musunuz', 'müsünüz', 'misiniz', 'mısınız',
 
354
  'ulaşamıyor', 'yapamıyor', 'gönderemiyor', 'edemiyor',
355
  '?'
356
  ]
357
+
358
+ # If message contains question indicators, it's likely not a product search
359
+ # EXCEPTION: If message contains a brand keyword, still search for products
360
+ if not contains_brand:
361
+ for indicator in question_indicators:
362
+ if indicator in clean_message:
363
+ return None
364
 
365
+ # Normalize cache key for consistent caching (Turkish chars + lowercase)
366
+ def normalize_for_cache(text):
367
+ """Normalize text for cache key"""
368
+ tr_map = {'İ': 'i', 'I': 'i', 'ı': 'i', 'Ğ': 'g', 'ğ': 'g', 'Ü': 'u', 'ü': 'u',
369
+ 'Ş': 's', 'ş': 's', 'Ö': 'o', 'ö': 'o', 'Ç': 'c', 'ç': 'c'}
370
+ for tr, en in tr_map.items():
371
+ text = text.replace(tr, en)
372
+ return text.lower().strip()
373
 
374
  # Check search cache first
375
+ cache_key = normalize_for_cache(user_message)
376
  current_time = time.time()
377
+
378
  if cache_key in cache['search_results']:
379
  cached = cache['search_results'][cache_key]
380
  if current_time - cached['time'] < CACHE_DURATION:
381
+ cache_age = (current_time - cached['time']) / 60 # in minutes
382
  return cached['data']
383
+ else:
384
+ pass
385
 
386
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
387
 
388
  # Check if user is asking about specific warehouse
389
  warehouse_keywords = {
390
  'caddebostan': 'Caddebostan',
391
+ 'ortaköy': 'Ortaköy',
392
  'ortakoy': 'Ortaköy',
393
  'alsancak': 'Alsancak',
394
  'izmir': 'Alsancak',
395
  'bahçeköy': 'Bahçeköy',
396
+ 'bahcekoy': 'Bahçeköy',
397
+ 'sarıyer': 'Bahçeköy',
398
+ 'sariyer': 'Bahçeköy'
399
  }
400
 
401
  user_lower = user_message.lower()
 
445
  if asked_warehouse:
446
  warehouse_filter = f"\nIMPORTANT: User is asking specifically about {asked_warehouse} warehouse. Only return products available in that warehouse."
447
 
448
+ # Debug logging
449
+ # Check if the target product exists
450
+ # Normalize Turkish characters for comparison
451
+ def normalize_turkish(text):
452
+ text = text.upper()
453
+ replacements = {'I': 'İ', 'Ç': 'C', 'Ş': 'S', 'Ğ': 'G', 'Ü': 'U', 'Ö': 'O'}
454
+ # Also try with İ -> I conversion
455
+ text2 = text.replace('İ', 'I')
456
+ return text, text2
457
+
458
+ search_term = user_message.upper()
459
+ search_norm1, search_norm2 = normalize_turkish(search_term)
460
+
461
+ matching_products = []
462
+ for p in products_summary:
463
+ p_name = p['name'].upper()
464
+ # Check both original and normalized versions
465
+ if (search_term in p_name or
466
+ search_norm1 in p_name or
467
+ search_norm2 in p_name or
468
+ search_term.replace('I', 'İ') in p_name):
469
+ matching_products.append(p)
470
+
471
+ if matching_products:
472
+ pass
473
+ else:
474
+ pass
475
+
476
+ # GPT-5 prompt with enhanced instructions
477
  smart_prompt = f"""User is asking: "{user_message}"
478
 
479
  FIRST CHECK: Is this actually a product search?
 
513
  - "45" (single product found)
514
  - "-1" (no products found)"""
515
 
516
+ # Check if we have API key before making the request
517
+ if not OPENAI_API_KEY:
518
+ # Try to find in Trek XML directly as fallback, but avoid tool products
519
+ user_message_normalized = user_message.upper()
520
+ tool_indicators = ['SUPER B', 'ANAHTAR', 'TAKIMI', 'PENSE', 'TOOL', 'ADAPTÖR', 'CONVERTER']
521
+ should_skip_trek_lookup = any(indicator in user_message_normalized for indicator in tool_indicators)
522
+
523
+ price, link = None, None
524
+ if not should_skip_trek_lookup:
525
+ price, link = get_product_price_and_link(user_message)
526
+
527
+ if price and link:
528
+ return [
529
+ f"🚲 **{user_message.title()}**",
530
+ f"💰 Fiyat: {price}",
531
+ f"🔗 Link: {link}",
532
+ "",
533
+ "⚠️ **Stok durumu kontrol edilemiyor**",
534
+ "📞 Güncel stok için mağazalarımızı arayın:",
535
+ "• Caddebostan: 0543 934 0438",
536
+ "• Alsancak: 0543 936 2335"
537
+ ]
538
+ return None
539
+
540
  headers = {
541
  "Content-Type": "application/json",
542
  "Authorization": f"Bearer {OPENAI_API_KEY}"
543
  }
544
 
545
+ # GPT-5.2 modeli temperature ve max_tokens desteklemiyor
546
  payload = {
547
  "model": "gpt-5.2-chat-latest",
548
  "messages": [
549
  {"role": "system", "content": "You are a product matcher. Find ALL matching products. Return only index numbers."},
550
  {"role": "user", "content": smart_prompt}
551
+ ]
 
 
552
  }
553
 
554
  try:
 
563
  result = response.json()
564
  indices_str = result['choices'][0]['message']['content'].strip()
565
 
566
+ # Handle empty response - try Trek XML as fallback, but avoid tool products
 
 
567
  if not indices_str or indices_str == "-1":
568
+ # Try to find in Trek XML directly, but skip tools
569
+ user_message_normalized = user_message.upper()
570
+ tool_indicators = ['SUPER B', 'ANAHTAR', 'TAKIMI', 'PENSE', 'TOOL', 'ADAPTÖR', 'CONVERTER']
571
+ should_skip_trek_lookup = any(indicator in user_message_normalized for indicator in tool_indicators)
572
+
573
+ price, link = None, None
574
+ if not should_skip_trek_lookup:
575
+ price, link = get_product_price_and_link(user_message)
576
+
577
+ if price and link:
578
+ # Found in Trek XML but not in warehouse stock!
579
+ return [
580
+ f"🚲 **{user_message.title()}**",
581
+ f"💰 Fiyat: {price}",
582
+ f"🔗 Link: {link}",
583
+ "",
584
+ "❌ **Stok Durumu: TÜKENDİ**",
585
+ "",
586
+ "📞 Stok güncellemesi veya ön sipariş için mağazalarımızı arayabilirsiniz:",
587
+ "• Caddebostan: 0543 934 0438",
588
+ "• Alsancak: 0543 936 2335"
589
+ ]
590
+ return None
591
 
592
  try:
593
  # Filter out empty strings and parse indices
 
608
  # Get product details
609
  name_match = re.search(r'<ProductName><!\[CDATA\[(.*?)\]\]></ProductName>', product_block)
610
  variant_match = re.search(r'<ProductVariant><!\[CDATA\[(.*?)\]\]></ProductVariant>', product_block)
611
+ productcode_match = re.search(r'<ProductCode><!\[CDATA\[(.*?)\]\]></ProductCode>', product_block)
612
 
613
  if name_match:
614
  product_name = name_match.group(1)
615
  variant = variant_match.group(1) if variant_match else ""
616
 
617
+ # Get price and link from Trek website - TRY SKU FIRST (NEW METHOD)
618
+ price, link = None, None
619
+
620
+ # Try SKU-based lookup first if ProductCode exists
621
+ product_code = productcode_match.group(1) if productcode_match else None
622
+ if product_code and product_code.strip():
623
+ price, link = get_product_price_and_link_by_sku(product_code.strip())
624
+
625
+ # Fallback to name-based if SKU didn't work, but be more careful about matching
626
+ if not price or not link:
627
+ # Only do name-based fallback if the product might reasonably be sold by Trek
628
+ # Avoid tools/accessories that clearly don't belong to Trek's bicycle catalog
629
+ product_name_normalized = product_name.upper()
630
+
631
+ # Skip name-based fallback for obvious tools/non-bike products
632
+ tool_indicators = ['SUPER B', 'ANAHTAR', 'TAKIMI', 'PENSE', 'TOOL', 'ADAPTÖR', 'CONVERTER']
633
+
634
+ should_skip_fallback = any(indicator in product_name_normalized for indicator in tool_indicators)
635
+
636
+ if not should_skip_fallback:
637
+ price, link = get_product_price_and_link(product_name, variant)
638
 
639
  variant_info = {
640
  'name': product_name,
 
718
  result.append(f"• {v['variant']}: {warehouses_str}")
719
 
720
  else:
721
+ # No warehouse stock found - check if product exists in Trek
722
+ # But be careful not to match tools/accessories with bikes
723
+ user_message_normalized = user_message.upper()
724
+ tool_indicators = ['SUPER B', 'ANAHTAR', 'TAKIMI', 'PENSE', 'TOOL', 'ADAPTÖR', 'CONVERTER']
725
+ should_skip_trek_lookup = any(indicator in user_message_normalized for indicator in tool_indicators)
726
+
727
+ price, link = None, None
728
+ if not should_skip_trek_lookup:
729
+ price, link = get_product_price_and_link(user_message)
730
+
731
+ if price and link:
732
+ result.append(f"❌ **Stok Durumu: TÜM MAĞAZALARDA TÜKENDİ**")
733
+ result.append("")
734
+ result.append(f"💰 Web Fiyatı: {price}")
735
+ result.append(f"🔗 Ürün Detayları: {link}")
736
+ result.append("")
737
+ result.append("📞 Stok güncellemesi veya ön sipariş için:")
738
+ result.append("• Caddebostan: 0543 934 0438")
739
+ result.append("• Alsancak: 0543 936 2335")
740
+ else:
741
+ return None
742
 
743
  # Cache the result before returning
744
  cache['search_results'][cache_key] = {
745
  'data': result,
746
  'time': current_time
747
  }
 
 
748
  return result
749
 
750
  except (ValueError, IndexError) as e:
 
751
  return None
752
  else:
 
753
  return None
754
 
755
  except Exception as e:
 
756
  return None
757
 
758
  def format_warehouse_name(wh_name):