BF-WAB / smart_warehouse_whatsapp.py
SamiKoen's picture
fix: Fix Turkish char normalization order in all files
032ddaf
"""Smart warehouse stock finder for WhatsApp - GPT-5 powered"""
import requests
import re
import os
import json
import xml.etree.ElementTree as ET
def get_product_price_and_link(product_name, variant=None):
"""Get price and link from Trek website XML"""
try:
url = 'https://www.trekbisiklet.com.tr/output/8582384479'
response = requests.get(url, verify=False, timeout=10)
if response.status_code != 200:
return None, None
root = ET.fromstring(response.content)
# Turkish character normalization FIRST (before lower)
tr_map = {'İ': 'i', 'I': 'i', 'ı': 'i', 'Ğ': 'g', 'ğ': 'g', 'Ü': 'u', 'ü': 'u', 'Ş': 's', 'ş': 's', 'Ö': 'o', 'ö': 'o', 'Ç': 'c', 'ç': 'c'}
# Apply normalization to original
search_name_norm = product_name
search_variant_norm = variant if variant else ""
for tr, en in tr_map.items():
search_name_norm = search_name_norm.replace(tr, en)
search_variant_norm = search_variant_norm.replace(tr, en)
# Now lowercase
search_name = search_name_norm.lower()
search_variant = search_variant_norm.lower()
best_match = None
best_score = 0
for item in root.findall('item'):
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)
# Calculate match score
score = 0
name_parts = search_name.split()
for part in name_parts:
if part in item_name:
score += 1
if variant and search_variant in item_name:
score += 2
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 for WhatsApp display
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 = None
# Extract link
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:
print(f"Error getting price/link: {e}")
return None, None
def get_warehouse_stock_gpt5(user_message):
"""GPT-5 powered smart warehouse stock search for WhatsApp"""
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
if not OPENAI_API_KEY:
return None
# Check for specific warehouse in query
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 warehouse XML with retry
xml_text = None
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
break
except:
if attempt == 2:
return None
if not xml_text:
return None
# Extract product blocks
product_pattern = r'<Product>(.*?)</Product>'
all_products = re.findall(product_pattern, xml_text, re.DOTALL)
# Create product summary for GPT
products_summary = []
for i, product_block in enumerate(all_products):
name_match = re.search(r'<ProductName><!\[CDATA\[(.*?)\]\]></ProductName>', product_block)
variant_match = re.search(r'<ProductVariant><!\[CDATA\[(.*?)\]\]></ProductVariant>', product_block)
if name_match:
warehouses_with_stock = []
warehouse_regex = r'<Warehouse>.*?<Name><!\[CDATA\[(.*?)\]\]></Name>.*?<Stock>(.*?)</Stock>.*?</Warehouse>'
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
if warehouses_with_stock: # Only add if has stock
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 GPT-5 prompt
warehouse_filter = ""
if asked_warehouse:
warehouse_filter = f"\nIMPORTANT: User is asking about {asked_warehouse} warehouse. Only return products available there."
smart_prompt = f"""User WhatsApp message: "{user_message}"
Find EXACT products matching this query. Be very precise with product names.
IMPORTANT: If user asks for "madone sl 6", find products with "MADONE SL 6" in the name.
DO NOT return similar products (like Marlin) if the exact product is not found.
Turkish/English terms:
- FORMA = jersey, TAYT = tights, İÇLİK = base layer, YAĞMURLUK = raincoat
- GOBIK = Spanish textile brand
- Sizes: S, M, L, XL, XXL, SMALL, MEDIUM, LARGE
{warehouse_filter}
Products with stock:
{json.dumps(products_summary, ensure_ascii=False)}
Return ONLY index numbers of EXACT matches as comma-separated list.
If NO exact match found, return: -1
Examples:
- "madone sl 6" -> Find only products with "MADONE SL 6" in name, NOT Marlin or other models
- "gobik forma" -> Find only products with "GOBIK" AND "FORMA" in name"""
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. Return only 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:
return None
result = response.json()
indices_str = result['choices'][0]['message']['content'].strip()
if not indices_str or indices_str == "-1":
return None
# Parse indices safely
indices = []
for idx in indices_str.split(','):
idx = idx.strip()
if idx and idx.isdigit():
indices.append(int(idx))
if not indices:
return None
# Collect matched products
matched_products = []
for idx in indices: # No limit, get all matches
if 0 <= idx < len(all_products):
product_block = all_products[idx]
name_match = re.search(r'<ProductName><!\[CDATA\[(.*?)\]\]></ProductName>', product_block)
variant_match = re.search(r'<ProductVariant><!\[CDATA\[(.*?)\]\]></ProductVariant>', product_block)
if name_match:
product_name = name_match.group(1)
variant = variant_match.group(1) if variant_match else ""
# Get price and link
price, link = get_product_price_and_link(product_name, variant)
# Get warehouse info
warehouses = []
warehouse_regex = r'<Warehouse>.*?<Name><!\[CDATA\[(.*?)\]\]></Name>.*?<Stock>(.*?)</Stock>.*?</Warehouse>'
wh_matches = re.findall(warehouse_regex, product_block, re.DOTALL)
for wh_name, wh_stock in wh_matches:
try:
if int(wh_stock.strip()) > 0:
if "CADDEBOSTAN" in wh_name:
warehouses.append("Caddebostan")
elif "ORTAKÖY" in wh_name:
warehouses.append("Ortaköy")
elif "ALSANCAK" in wh_name:
warehouses.append("Alsancak")
elif "BAHCEKOY" in wh_name or "BAHÇEKÖY" in wh_name:
warehouses.append("Bahçeköy")
except:
pass
if warehouses:
matched_products.append({
'name': product_name,
'variant': variant,
'price': price,
'link': link,
'warehouses': warehouses
})
return matched_products if matched_products else None
except Exception as e:
print(f"GPT-5 search error: {e}")
return None