|
"""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) |
|
|
|
|
|
tr_map = {'İ': 'i', 'I': 'i', 'ı': 'i', 'Ğ': 'g', 'ğ': 'g', 'Ü': 'u', 'ü': 'u', 'Ş': 's', 'ş': 's', 'Ö': 'o', 'ö': 'o', 'Ç': 'c', 'ç': 'c'} |
|
|
|
|
|
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) |
|
|
|
|
|
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) |
|
|
|
|
|
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: |
|
|
|
price_elem = best_match.find('priceTaxWithCur') |
|
price = price_elem.text if price_elem is not None and price_elem.text else None |
|
|
|
|
|
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 |
|
|
|
|
|
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 |
|
|
|
|
|
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 |
|
|
|
|
|
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 |
|
|
|
|
|
product_pattern = r'<Product>(.*?)</Product>' |
|
all_products = re.findall(product_pattern, xml_text, re.DOTALL) |
|
|
|
|
|
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: |
|
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) |
|
|
|
|
|
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 |
|
|
|
|
|
indices = [] |
|
for idx in indices_str.split(','): |
|
idx = idx.strip() |
|
if idx and idx.isdigit(): |
|
indices.append(int(idx)) |
|
|
|
if not indices: |
|
return None |
|
|
|
|
|
matched_products = [] |
|
for idx in indices: |
|
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 "" |
|
|
|
|
|
price, link = get_product_price_and_link(product_name, variant) |
|
|
|
|
|
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 |