"""Smart warehouse stock finder with price and link information"""
import requests
import re
import os
import json
import xml.etree.ElementTree as ET
import time
# Cache configuration - 2 hours (reduced from 12 hours for more accurate results)
CACHE_DURATION = 7200 # 2 hours
cache = {
'warehouse_xml': {'data': None, 'time': 0},
'trek_xml': {'data': None, 'time': 0},
'products_summary': {'data': None, 'time': 0},
'search_results': {} # Cache for specific searches
}
def get_cached_trek_xml():
"""Get Trek XML with 12-hour caching"""
current_time = time.time()
if cache['trek_xml']['data'] and (current_time - cache['trek_xml']['time'] < CACHE_DURATION):
cache_age = (current_time - cache['trek_xml']['time']) / 60 # in minutes
return cache['trek_xml']['data']
try:
url = 'https://www.trekbisiklet.com.tr/output/8582384479'
response = requests.get(url, verify=False, timeout=10)
if response.status_code == 200:
cache['trek_xml']['data'] = response.content
cache['trek_xml']['time'] = current_time
return response.content
else:
return None
except Exception as e:
return None
def get_product_price_and_link(product_name, variant=None):
"""Get price and link from Trek website XML"""
try:
# Get cached Trek XML
xml_content = get_cached_trek_xml()
if not xml_content:
return None, None
root = ET.fromstring(xml_content)
# Turkish character normalization FIRST (before lower())
tr_map = {
'İ': 'i', 'I': 'i', 'ı': 'i', # All I variations to i
'Ğ': 'g', 'ğ': 'g',
'Ü': 'u', 'ü': 'u',
'Ş': 's', 'ş': 's',
'Ö': 'o', 'ö': 'o',
'Ç': 'c', 'ç': 'c'
}
# Apply normalization to original (before lower)
search_name_normalized = product_name
search_variant_normalized = variant if variant else ""
for tr, en in tr_map.items():
search_name_normalized = search_name_normalized.replace(tr, en)
search_variant_normalized = search_variant_normalized.replace(tr, en)
# Now lowercase
search_name = search_name_normalized.lower()
search_variant = search_variant_normalized.lower()
best_match = None
best_score = 0
# Clean search name - remove year and parentheses
clean_search = re.sub(r'\s*\(\d{4}\)\s*', '', search_name).strip()
for item in root.findall('item'):
# Get product name
rootlabel_elem = item.find('rootlabel')
if rootlabel_elem is None or not rootlabel_elem.text:
continue
item_name = rootlabel_elem.text.lower()
for tr, en in tr_map.items():
item_name = item_name.replace(tr, en)
# Clean item name too
clean_item = re.sub(r'\s*\(\d{4}\)\s*', '', item_name).strip()
# Calculate match score with priority for exact matches
score = 0
# Exact match gets highest priority
if clean_search == clean_item:
score += 100
# Check if starts with exact product name (e.g., "fx 2" in "fx 2 kirmizi")
elif clean_item.startswith(clean_search + " ") or clean_item == clean_search:
score += 50
else:
# Partial matching
name_parts = clean_search.split()
for part in name_parts:
if part in clean_item:
score += 1
# Check variant if specified
if variant and search_variant in item_name:
score += 2 # Variant match is important
if score > best_score:
best_score = score
best_match = item
if best_match and best_score > 0:
# Extract price
price_elem = best_match.find('priceTaxWithCur')
price = price_elem.text if price_elem is not None and price_elem.text else None
# Round price
if price:
try:
price_float = float(price)
if price_float > 200000:
rounded = round(price_float / 5000) * 5000
price = f"{int(rounded):,}".replace(',', '.') + " TL"
elif price_float > 30000:
rounded = round(price_float / 1000) * 1000
price = f"{int(rounded):,}".replace(',', '.') + " TL"
elif price_float > 10000:
rounded = round(price_float / 100) * 100
price = f"{int(rounded):,}".replace(',', '.') + " TL"
else:
rounded = round(price_float / 10) * 10
price = f"{int(rounded):,}".replace(',', '.') + " TL"
except:
price = f"{price} TL"
# Extract link (field name is productLink, not productUrl!)
link_elem = best_match.find('productLink')
link = link_elem.text if link_elem is not None and link_elem.text else None
return price, link
return None, None
except Exception as e:
return None, None
def get_cached_warehouse_xml():
"""Get warehouse XML with 12-hour caching"""
current_time = time.time()
if cache['warehouse_xml']['data'] and (current_time - cache['warehouse_xml']['time'] < CACHE_DURATION):
cache_age = (current_time - cache['warehouse_xml']['time']) / 60 # in minutes
return cache['warehouse_xml']['data']
for attempt in range(3):
try:
url = 'https://video.trek-turkey.com/bizimhesap-warehouse-xml-b2b-api-v2.php'
timeout_val = 10 + (attempt * 5)
response = requests.get(url, verify=False, timeout=timeout_val)
xml_text = response.text
cache['warehouse_xml']['data'] = xml_text
cache['warehouse_xml']['time'] = current_time
return xml_text
except requests.exceptions.Timeout:
if attempt == 2:
return None
except Exception as e:
return None
return None
def get_warehouse_stock_smart_with_price(user_message, previous_result=None):
"""Enhanced smart warehouse search with price and link info"""
# Filter out common non-product words and responses
non_product_words = [
'süper', 'harika', 'güzel', 'teşekkürler', 'teşekkür', 'tamam', 'olur',
'evet', 'hayır', 'merhaba', 'selam', 'iyi', 'kötü', 'fena', 'muhteşem',
'mükemmel', 'berbat', 'idare eder', 'olabilir', 'değil', 'var', 'yok',
'anladım', 'anlaşıldı', 'peki', 'tamamdır', 'ok', 'okay', 'aynen',
'kesinlikle', 'elbette', 'tabii', 'tabiki', 'doğru', 'yanlış'
]
# Check if message is just a simple response
clean_message = user_message.lower().strip()
if clean_message in non_product_words:
return None
# Check if it's a single word that's likely not a product
if len(clean_message.split()) == 1 and len(clean_message) < 5:
# Short single words are usually not product names
return None
# Check if this is a question rather than a product search
question_indicators = [
'musun', 'müsün', 'misin', 'mısın', 'miyim', 'mıyım',
'musunuz', 'müsünüz', 'misiniz', 'mısınız',
'neden', 'nasıl', 'ne zaman', 'kim', 'nerede', 'nereye',
'ulaşamıyor', 'yapamıyor', 'gönderemiyor', 'edemiyor',
'?'
]
# If message contains question indicators, it's likely not a product search
for indicator in question_indicators:
if indicator in clean_message:
return None
# Normalize cache key for consistent caching (Turkish chars + lowercase)
def normalize_for_cache(text):
"""Normalize text for cache key"""
tr_map = {'İ': 'i', 'I': 'i', 'ı': 'i', 'Ğ': 'g', 'ğ': 'g', 'Ü': 'u', 'ü': 'u',
'Ş': 's', 'ş': 's', 'Ö': 'o', 'ö': 'o', 'Ç': 'c', 'ç': 'c'}
for tr, en in tr_map.items():
text = text.replace(tr, en)
return text.lower().strip()
# Check search cache first
cache_key = normalize_for_cache(user_message)
current_time = time.time()
if cache_key in cache['search_results']:
cached = cache['search_results'][cache_key]
if current_time - cached['time'] < CACHE_DURATION:
cache_age = (current_time - cached['time']) / 60 # in minutes
return cached['data']
else:
pass
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
# Check if user is asking about specific warehouse
warehouse_keywords = {
'caddebostan': 'Caddebostan',
'ortaköy': 'Ortaköy',
'ortakoy': 'Ortaköy',
'alsancak': 'Alsancak',
'izmir': 'Alsancak',
'bahçeköy': 'Bahçeköy',
'bahcekoy': 'Bahçeköy'
}
user_lower = user_message.lower()
asked_warehouse = None
for keyword, warehouse in warehouse_keywords.items():
if keyword in user_lower:
asked_warehouse = warehouse
break
# Get cached XML data
xml_text = get_cached_warehouse_xml()
if not xml_text:
return None
# Extract product blocks
product_pattern = r'(.*?)'
all_products = re.findall(product_pattern, xml_text, re.DOTALL)
# Create simplified product list for GPT
products_summary = []
for i, product_block in enumerate(all_products):
name_match = re.search(r'', product_block)
variant_match = re.search(r'', product_block)
if name_match:
warehouses_with_stock = []
warehouse_regex = r'.*?.*?(.*?).*?'
warehouses = re.findall(warehouse_regex, product_block, re.DOTALL)
for wh_name, wh_stock in warehouses:
try:
if int(wh_stock.strip()) > 0:
warehouses_with_stock.append(wh_name)
except:
pass
product_info = {
"index": i,
"name": name_match.group(1),
"variant": variant_match.group(1) if variant_match else "",
"warehouses": warehouses_with_stock
}
products_summary.append(product_info)
# Prepare warehouse filter if needed
warehouse_filter = ""
if asked_warehouse:
warehouse_filter = f"\nIMPORTANT: User is asking specifically about {asked_warehouse} warehouse. Only return products available in that warehouse."
# Debug logging
# Check if the target product exists
# Normalize Turkish characters for comparison
def normalize_turkish(text):
text = text.upper()
replacements = {'I': 'İ', 'Ç': 'C', 'Ş': 'S', 'Ğ': 'G', 'Ü': 'U', 'Ö': 'O'}
# Also try with İ -> I conversion
text2 = text.replace('İ', 'I')
return text, text2
search_term = user_message.upper()
search_norm1, search_norm2 = normalize_turkish(search_term)
matching_products = []
for p in products_summary:
p_name = p['name'].upper()
# Check both original and normalized versions
if (search_term in p_name or
search_norm1 in p_name or
search_norm2 in p_name or
search_term.replace('I', 'İ') in p_name):
matching_products.append(p)
if matching_products:
pass
else:
pass
# GPT-5 prompt with enhanced instructions
smart_prompt = f"""User is asking: "{user_message}"
FIRST CHECK: Is this actually a product search?
- If the message is a question about the system, service, or a general inquiry, return: -1
- If the message contains "musun", "misin", "neden", "nasıl", etc. it's likely NOT a product search
- Only proceed if this looks like a genuine product name or model
Find ALL products that match this query from the list below.
If user asks about specific size (S, M, L, XL, XXL, SMALL, MEDIUM, LARGE, X-LARGE), return only that size.
If user asks generally (without size), return ALL variants of the product.
{warehouse_filter}
CRITICAL TURKISH CHARACTER RULES:
- "MARLIN" and "MARLİN" are the SAME product (Turkish İ vs I)
- Treat these as equivalent: I/İ/ı, Ö/ö, Ü/ü, Ş/ş, Ğ/ğ, Ç/ç
- If user writes "Marlin", also match "MARLİN" in the list
IMPORTANT BRAND AND PRODUCT TYPE RULES:
- GOBIK: Spanish textile brand we import. When user asks about "gobik", return ALL products with "GOBIK" in the name.
- Product names contain type information: FORMA (jersey/cycling shirt), TAYT (tights), İÇLİK (base layer), YAĞMURLUK (raincoat), etc.
- Understand Turkish/English terms:
* "erkek forma" / "men's jersey" -> Find products with FORMA in name
* "tayt" / "tights" -> Find products with TAYT in name
* "içlik" / "base layer" -> Find products with İÇLİK in name
* "yağmurluk" / "raincoat" -> Find products with YAĞMURLUK in name
- Gender: UNISEX means for both men and women. If no gender specified, it's typically men's.
Products list (with warehouse availability):
{json.dumps(products_summary, ensure_ascii=False, indent=2)}
Return ONLY index numbers of ALL matching products as comma-separated list (e.g., "5,8,12,15").
If no products found, return ONLY: -1
DO NOT return empty string or any explanation, ONLY numbers or -1
Examples of correct responses:
- "2,5,8,12,15,20" (multiple products found)
- "45" (single product found)
- "-1" (no products found)"""
# Check if we have API key before making the request
if not OPENAI_API_KEY:
# Try to find in Trek XML directly as fallback
price, link = get_product_price_and_link(user_message)
if price and link:
return [
f"🚲 **{user_message.title()}**",
f"💰 Fiyat: {price}",
f"🔗 Link: {link}",
"",
"⚠️ **Stok durumu kontrol edilemiyor**",
"📞 Güncel stok için mağazalarımızı arayın:",
"• Caddebostan: 0216 123 45 67",
"• Alsancak: 0232 987 65 43"
]
return None
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {OPENAI_API_KEY}"
}
payload = {
"model": "gpt-5-chat-latest",
"messages": [
{"role": "system", "content": "You are a product matcher. Find ALL matching products. Return only index numbers."},
{"role": "user", "content": smart_prompt}
],
"temperature": 0,
"max_tokens": 100
}
try:
response = requests.post(
"https://api.openai.com/v1/chat/completions",
headers=headers,
json=payload,
timeout=10
)
if response.status_code == 200:
result = response.json()
indices_str = result['choices'][0]['message']['content'].strip()
# Handle empty response - try Trek XML as fallback
if not indices_str or indices_str == "-1":
# Try to find in Trek XML directly
price, link = get_product_price_and_link(user_message)
if price and link:
# Found in Trek XML but not in warehouse stock!
return [
f"🚲 **{user_message.title()}**",
f"💰 Fiyat: {price}",
f"🔗 Link: {link}",
"",
"❌ **Stok Durumu: TÜKENDİ**",
"",
"📞 Stok güncellemesi veya ön sipariş için mağazalarımızı arayabilirsiniz:",
"• Caddebostan: 0216 123 45 67",
"• Alsancak: 0232 987 65 43"
]
return [f"❌ {user_message} bulunamadı. Lütfen ürün adını kontrol edin."]
try:
# Filter out empty strings and parse indices
indices = []
for idx in indices_str.split(','):
idx = idx.strip()
if idx and idx.isdigit():
indices.append(int(idx))
# Collect all matching products with price/link
all_variants = []
warehouse_stock = {}
for idx in indices:
if 0 <= idx < len(all_products):
product_block = all_products[idx]
# Get product details
name_match = re.search(r'', product_block)
variant_match = re.search(r'', product_block)
if name_match:
product_name = name_match.group(1)
variant = variant_match.group(1) if variant_match else ""
# Get price and link from Trek website
price, link = get_product_price_and_link(product_name, variant)
variant_info = {
'name': product_name,
'variant': variant,
'price': price,
'link': link,
'warehouses': []
}
# Get warehouse stock
warehouse_regex = r'.*?.*?(.*?).*?'
warehouses = re.findall(warehouse_regex, product_block, re.DOTALL)
for wh_name, wh_stock in warehouses:
try:
stock = int(wh_stock.strip())
if stock > 0:
display_name = format_warehouse_name(wh_name)
variant_info['warehouses'].append({
'name': display_name,
'stock': stock
})
if display_name not in warehouse_stock:
warehouse_stock[display_name] = 0
warehouse_stock[display_name] += stock
except:
pass
if variant_info['warehouses']:
all_variants.append(variant_info)
# Format result
result = []
if asked_warehouse:
# Filter for specific warehouse
warehouse_variants = []
for variant in all_variants:
for wh in variant['warehouses']:
if asked_warehouse in wh['name']:
warehouse_variants.append(variant)
break
if warehouse_variants:
result.append(f"{format_warehouse_name(asked_warehouse)} mağazasında mevcut:")
for v in warehouse_variants:
variant_text = f" ({v['variant']})" if v['variant'] else ""
result.append(f"• {v['name']}{variant_text}")
if v['price']:
result.append(f" Fiyat: {v['price']}")
if v['link']:
result.append(f" Link: {v['link']}")
else:
result.append(f"{format_warehouse_name(asked_warehouse)} mağazasında bu ürün mevcut değil")
else:
# Show all variants
if all_variants:
# Group by product name for cleaner display
product_groups = {}
for variant in all_variants:
if variant['name'] not in product_groups:
product_groups[variant['name']] = []
product_groups[variant['name']].append(variant)
result.append(f"Bulunan ürünler:")
for product_name, variants in product_groups.items():
result.append(f"\n{product_name}:")
# Show first variant's price and link (usually same for all variants)
if variants[0]['price']:
result.append(f"Fiyat: {variants[0]['price']}")
if variants[0]['link']:
result.append(f"Link: {variants[0]['link']}")
# Show variants and their availability
for v in variants:
if v['variant']:
warehouses_str = ", ".join([w['name'].replace(' mağazası', '') for w in v['warehouses']])
result.append(f"• {v['variant']}: {warehouses_str}")
else:
# No warehouse stock found - check if product exists in Trek
price, link = get_product_price_and_link(user_message)
if price and link:
result.append(f"❌ **Stok Durumu: TÜM MAĞAZALARDA TÜKENDİ**")
result.append("")
result.append(f"💰 Web Fiyatı: {price}")
result.append(f"🔗 Ürün Detayları: {link}")
result.append("")
result.append("📞 Stok güncellemesi veya ön sipariş için:")
result.append("• Caddebostan: 0216 123 45 67")
result.append("• Alsancak: 0232 987 65 43")
else:
result.append(f"❌ Ürün bulunamadı")
# Cache the result before returning
cache['search_results'][cache_key] = {
'data': result,
'time': current_time
}
return result
except (ValueError, IndexError) as e:
return None
else:
return None
except Exception as e:
return None
def format_warehouse_name(wh_name):
"""Format warehouse name nicely"""
if "CADDEBOSTAN" in wh_name:
return "Caddebostan mağazası"
elif "ORTAKÖY" in wh_name:
return "Ortaköy mağazası"
elif "ALSANCAK" in wh_name:
return "İzmir Alsancak mağazası"
elif "BAHCEKOY" in wh_name or "BAHÇEKÖY" in wh_name:
return "Bahçeköy mağazası"
else:
return wh_name.replace("MAGAZA DEPO", "").strip()