|
|
"""Combined API Search Module - Yeni tek API'yi kullanan arama sistemi""" |
|
|
|
|
|
import requests |
|
|
import xml.etree.ElementTree as ET |
|
|
import os |
|
|
import time |
|
|
import logging |
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
def normalize_turkish(text): |
|
|
"""Türkçe karakter normalizasyonu""" |
|
|
if not text: |
|
|
return "" |
|
|
|
|
|
|
|
|
text = text.upper() |
|
|
|
|
|
|
|
|
replacements = { |
|
|
'İ': 'I', 'I': 'I', 'ı': 'I', 'Ì': 'I', 'Í': 'I', 'Î': 'I', 'Ï': 'I', |
|
|
'Ş': 'S', 'ş': 'S', 'Ș': 'S', 'ș': 'S', 'Ś': 'S', |
|
|
'Ğ': 'G', 'ğ': 'G', 'Ĝ': 'G', 'ĝ': 'G', |
|
|
'Ü': 'U', 'ü': 'U', 'Û': 'U', 'û': 'U', 'Ù': 'U', 'Ú': 'U', |
|
|
'Ö': 'O', 'ö': 'O', 'Ô': 'O', 'ô': 'O', 'Ò': 'O', 'Ó': 'O', |
|
|
'Ç': 'C', 'ç': 'C', 'Ć': 'C', 'ć': 'C', |
|
|
'Â': 'A', 'â': 'A', 'À': 'A', 'Á': 'A', 'Ä': 'A', |
|
|
'Ê': 'E', 'ê': 'E', 'È': 'E', 'É': 'E', 'Ë': 'E' |
|
|
} |
|
|
|
|
|
for tr_char, en_char in replacements.items(): |
|
|
text = text.replace(tr_char, en_char) |
|
|
|
|
|
|
|
|
import re |
|
|
text = re.sub(r'\s+', ' ', text) |
|
|
|
|
|
return text.strip() |
|
|
|
|
|
|
|
|
CACHE_DURATION = 3600 |
|
|
cache = { |
|
|
'combined_api': {'data': None, 'time': 0} |
|
|
} |
|
|
|
|
|
def get_combined_api_data(): |
|
|
"""Combined API'den veriyi çek ve cache'le""" |
|
|
current_time = time.time() |
|
|
|
|
|
|
|
|
if cache['combined_api']['data'] and (current_time - cache['combined_api']['time'] < CACHE_DURATION): |
|
|
cache_age = (current_time - cache['combined_api']['time']) / 60 |
|
|
logger.info(f"📦 Combined API cache kullanılıyor (yaş: {cache_age:.1f} dakika)") |
|
|
return cache['combined_api']['data'] |
|
|
|
|
|
try: |
|
|
|
|
|
url = 'https://video.trek-turkey.com/combined_trek_xml_api_v2.php' |
|
|
response = requests.get(url, timeout=30) |
|
|
|
|
|
if response.status_code == 200: |
|
|
|
|
|
root = ET.fromstring(response.content) |
|
|
|
|
|
|
|
|
cache['combined_api']['data'] = root |
|
|
cache['combined_api']['time'] = current_time |
|
|
|
|
|
|
|
|
stats = root.find('stats') |
|
|
if stats is not None: |
|
|
matched = stats.find('matched_products') |
|
|
match_rate = stats.find('match_rate') |
|
|
if matched is not None and match_rate is not None: |
|
|
logger.info(f"✅ Combined API: {matched.text} ürün eşleşti ({match_rate.text})") |
|
|
|
|
|
return root |
|
|
else: |
|
|
logger.error(f"❌ Combined API HTTP Error: {response.status_code}") |
|
|
return None |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"❌ Combined API Hata: {e}") |
|
|
return None |
|
|
|
|
|
def search_products_combined_api(query): |
|
|
"""Yeni combined API kullanarak ürün ara""" |
|
|
try: |
|
|
|
|
|
root = get_combined_api_data() |
|
|
if not root: |
|
|
return [] |
|
|
|
|
|
|
|
|
stop_words = ['VAR', 'MI', 'MEVCUT', 'MU', 'STOK', 'STOGU', 'DURUMU', 'BULUNUYOR'] |
|
|
|
|
|
|
|
|
query_words = normalize_turkish(query).upper().split() |
|
|
query_words = [word for word in query_words if word not in stop_words] |
|
|
query_normalized = ' '.join(query_words) |
|
|
|
|
|
results = [] |
|
|
|
|
|
|
|
|
for product in root.findall('product'): |
|
|
|
|
|
stock_code = product.find('stock_code') |
|
|
name = product.find('name') |
|
|
label = product.find('label') |
|
|
url = product.find('url') |
|
|
image_url = product.find('image_url') |
|
|
|
|
|
|
|
|
prices = product.find('prices') |
|
|
regular_price = None |
|
|
discounted_price = None |
|
|
|
|
|
if prices is not None: |
|
|
regular_price_elem = prices.find('regular_price') |
|
|
discounted_price_elem = prices.find('discounted_price') |
|
|
|
|
|
if regular_price_elem is not None: |
|
|
regular_price = regular_price_elem.text |
|
|
if discounted_price_elem is not None: |
|
|
discounted_price = discounted_price_elem.text |
|
|
|
|
|
|
|
|
category = product.find('category') |
|
|
main_category = None |
|
|
sub_category = None |
|
|
|
|
|
if category is not None: |
|
|
main_cat_elem = category.find('main_category') |
|
|
sub_cat_elem = category.find('sub_category') |
|
|
|
|
|
if main_cat_elem is not None: |
|
|
main_category = main_cat_elem.text |
|
|
if sub_cat_elem is not None: |
|
|
sub_category = sub_cat_elem.text |
|
|
|
|
|
|
|
|
warehouse_stock = product.find('warehouse_stock') |
|
|
stock_found = False |
|
|
total_stock = 0 |
|
|
warehouses = [] |
|
|
|
|
|
if warehouse_stock is not None: |
|
|
found_attr = warehouse_stock.get('found') |
|
|
stock_found = found_attr == 'true' |
|
|
|
|
|
if stock_found: |
|
|
total_stock_elem = warehouse_stock.find('total_stock') |
|
|
if total_stock_elem is not None: |
|
|
total_stock = int(total_stock_elem.text) |
|
|
|
|
|
|
|
|
warehouses_elem = warehouse_stock.find('warehouses') |
|
|
if warehouses_elem is not None: |
|
|
for wh in warehouses_elem.findall('warehouse'): |
|
|
wh_name = wh.find('name') |
|
|
wh_stock = wh.find('stock') |
|
|
|
|
|
if wh_name is not None and wh_stock is not None: |
|
|
warehouses.append({ |
|
|
'name': wh_name.text, |
|
|
'stock': int(wh_stock.text) |
|
|
}) |
|
|
|
|
|
|
|
|
searchable_text = "" |
|
|
if name is not None: |
|
|
searchable_text += normalize_turkish(name.text) + " " |
|
|
if label is not None: |
|
|
searchable_text += normalize_turkish(label.text) + " " |
|
|
if stock_code is not None: |
|
|
searchable_text += normalize_turkish(stock_code.text) + " " |
|
|
if main_category is not None: |
|
|
searchable_text += normalize_turkish(main_category) + " " |
|
|
if sub_category is not None: |
|
|
searchable_text += normalize_turkish(sub_category) + " " |
|
|
|
|
|
|
|
|
filtered_query_words = query_words |
|
|
name_normalized = normalize_turkish(name.text.upper()) if name is not None else "" |
|
|
|
|
|
|
|
|
basic_matches = sum(1 for word in filtered_query_words if word in searchable_text) |
|
|
|
|
|
if basic_matches > 0: |
|
|
|
|
|
exact_bonus = 10 if all(word in name_normalized for word in filtered_query_words) else 0 |
|
|
|
|
|
|
|
|
sequence_bonus = 0 |
|
|
if len(filtered_query_words) >= 2: |
|
|
query_sequence = " ".join(filtered_query_words) |
|
|
if query_sequence in name_normalized: |
|
|
sequence_bonus = 5 |
|
|
|
|
|
|
|
|
length_penalty = max(0, len(name_normalized.split()) - len(filtered_query_words)) * -1 |
|
|
|
|
|
|
|
|
total_score = basic_matches + exact_bonus + sequence_bonus + length_penalty |
|
|
|
|
|
result = { |
|
|
'stock_code': stock_code.text if stock_code is not None else '', |
|
|
'name': name.text if name is not None else '', |
|
|
'label': label.text if label is not None else '', |
|
|
'url': url.text if url is not None else '', |
|
|
'image_url': image_url.text if image_url is not None else '', |
|
|
'regular_price': regular_price, |
|
|
'discounted_price': discounted_price, |
|
|
'main_category': main_category, |
|
|
'sub_category': sub_category, |
|
|
'stock_found': stock_found, |
|
|
'total_stock': total_stock, |
|
|
'warehouses': warehouses, |
|
|
'match_score': total_score |
|
|
} |
|
|
results.append(result) |
|
|
|
|
|
|
|
|
results.sort(key=lambda x: x['match_score'], reverse=True) |
|
|
|
|
|
logger.info(f"🔍 Combined API araması: '{query}' için {len(results)} sonuç") |
|
|
return results[:10] |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"❌ Combined API arama hatası: {e}") |
|
|
return [] |
|
|
|
|
|
def format_product_result_whatsapp(product_data, show_variants_info=False, all_results=None): |
|
|
"""Ürün bilgisini WhatsApp için formatla""" |
|
|
try: |
|
|
name = product_data['name'] |
|
|
url = product_data['url'] |
|
|
image_url = product_data['image_url'] |
|
|
regular_price = product_data['regular_price'] |
|
|
discounted_price = product_data['discounted_price'] |
|
|
stock_found = product_data['stock_found'] |
|
|
total_stock = product_data['total_stock'] |
|
|
warehouses = product_data['warehouses'] |
|
|
|
|
|
result = [] |
|
|
|
|
|
|
|
|
result.append(f"🚲 **{name}**") |
|
|
|
|
|
|
|
|
def format_price(price_str): |
|
|
if not price_str: |
|
|
return "" |
|
|
try: |
|
|
price_float = float(price_str) |
|
|
|
|
|
if price_float > 200000: |
|
|
rounded = round(price_float / 5000) * 5000 |
|
|
elif price_float > 30000: |
|
|
rounded = round(price_float / 1000) * 1000 |
|
|
elif price_float > 10000: |
|
|
rounded = round(price_float / 100) * 100 |
|
|
else: |
|
|
rounded = round(price_float / 10) * 10 |
|
|
|
|
|
|
|
|
return f"{rounded:,.0f}".replace(",", ".") |
|
|
except: |
|
|
return price_str |
|
|
|
|
|
if regular_price: |
|
|
formatted_regular = format_price(regular_price) |
|
|
if discounted_price and discounted_price != regular_price: |
|
|
formatted_discounted = format_price(discounted_price) |
|
|
result.append(f"💰 Fiyat: ~~{formatted_regular}~~ **{formatted_discounted} TL**") |
|
|
result.append("🔥 İndirimli fiyat!") |
|
|
else: |
|
|
result.append(f"💰 Fiyat: {formatted_regular} TL") |
|
|
|
|
|
|
|
|
if stock_found and total_stock > 0: |
|
|
|
|
|
if warehouses: |
|
|
|
|
|
if show_variants_info and all_results and is_main_product(name): |
|
|
result.append("📦 **Mevcut varyantlar:**") |
|
|
|
|
|
|
|
|
main_name_base = name.upper() |
|
|
variant_details = {} |
|
|
|
|
|
for product in all_results: |
|
|
if (main_name_base in product['name'].upper() and |
|
|
product['stock_found'] and |
|
|
product['total_stock'] > 0 and |
|
|
not is_main_product(product['name'])): |
|
|
|
|
|
|
|
|
variant_name = product['name'] |
|
|
variant_part = variant_name.replace(main_name_base, '').strip() |
|
|
if variant_part.startswith('GEN 3 (2026)'): |
|
|
variant_part = variant_part.replace('GEN 3 (2026)', '').strip() |
|
|
|
|
|
for wh in product['warehouses']: |
|
|
if wh['stock'] > 0: |
|
|
store_name = wh['name'] |
|
|
if store_name not in variant_details: |
|
|
variant_details[store_name] = [] |
|
|
variant_details[store_name].append(variant_part) |
|
|
|
|
|
|
|
|
for store, variants in variant_details.items(): |
|
|
result.append(f"• **{store}:** {', '.join(variants)}") |
|
|
|
|
|
|
|
|
result.append("📞 Diğer beden/renk teyidi için mağazaları arayın") |
|
|
else: |
|
|
|
|
|
available_stores = [wh['name'] for wh in warehouses if wh['stock'] > 0] |
|
|
if available_stores: |
|
|
result.append("📦 **Stokta mevcut mağazalar:**") |
|
|
for store in available_stores: |
|
|
result.append(f"• {store}") |
|
|
else: |
|
|
result.append("⚠️ **Stok durumu kontrol edilemiyor**") |
|
|
result.append("📞 Güncel stok için mağazalarımızı arayın:") |
|
|
result.append("• Caddebostan: 0543 934 0438") |
|
|
result.append("• Alsancak: 0543 936 2335") |
|
|
|
|
|
|
|
|
if url: |
|
|
result.append(f"🔗 [Ürün Detayları]({url})") |
|
|
|
|
|
|
|
|
if image_url and image_url.startswith('https://'): |
|
|
|
|
|
result.append(f"📷 {image_url}") |
|
|
|
|
|
return "\n".join(result) |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"❌ WhatsApp format hatası: {e}") |
|
|
return "Ürün bilgisi formatlanamadı" |
|
|
|
|
|
def is_main_product(product_name): |
|
|
"""Ürün adına bakarak ana ürün mü varyant mı kontrol et""" |
|
|
name_upper = product_name.upper() |
|
|
|
|
|
|
|
|
variant_indicators = [ |
|
|
|
|
|
' - XS', ' - S', ' - M', ' - L', ' - XL', ' - XXL', |
|
|
' XS', ' S ', ' M ', ' L ', ' XL', ' XXL', |
|
|
|
|
|
|
|
|
' - SİYAH', ' - MAVİ', ' - KIRMIZI', ' - YEŞİL', ' - BEYAZ', ' - MOR', |
|
|
' SİYAH', ' MAVİ', ' KIRMIZI', ' YEŞİL', ' BEYAZ', ' MOR', |
|
|
|
|
|
|
|
|
' - BLACK', ' - BLUE', ' - RED', ' - GREEN', ' - WHITE', ' - PURPLE', |
|
|
' BLACK', ' BLUE', ' RED', ' GREEN', ' WHITE', ' PURPLE' |
|
|
] |
|
|
|
|
|
|
|
|
for indicator in variant_indicators: |
|
|
if indicator in name_upper: |
|
|
return False |
|
|
|
|
|
return True |
|
|
|
|
|
def get_warehouse_stock_combined_api(query): |
|
|
"""Combined API kullanan warehouse search - eski fonksiyonla uyumlu""" |
|
|
results = search_products_combined_api(query) |
|
|
|
|
|
if not results: |
|
|
return ["Ürün bulunamadı"] |
|
|
|
|
|
|
|
|
|
|
|
query_normalized = normalize_turkish(query.upper()) |
|
|
|
|
|
|
|
|
clean_query = query_normalized |
|
|
for suffix in ['VAR MI', 'VAR MIYDI', 'STOK', 'STOKTA', 'FIYAT', 'FIYATI', 'KACA', 'NE KADAR']: |
|
|
clean_query = clean_query.replace(' ' + suffix, '').replace(suffix + ' ', '').replace(suffix, '') |
|
|
clean_query = clean_query.strip() |
|
|
|
|
|
logger.info(f"🧹 Query temizlendi: '{query}' → '{clean_query}'") |
|
|
|
|
|
|
|
|
if not any(year in clean_query for year in ['GEN', '2024', '2025', '2026', '(20']): |
|
|
|
|
|
stocked_alternatives = [] |
|
|
|
|
|
for product in results: |
|
|
name_normalized = normalize_turkish(product['name'].upper()) |
|
|
|
|
|
if clean_query in name_normalized: |
|
|
|
|
|
if is_main_product(product['name']): |
|
|
|
|
|
if product['stock_found'] and product['total_stock'] > 0: |
|
|
stocked_alternatives.append(product) |
|
|
else: |
|
|
|
|
|
has_stocked_variants = False |
|
|
for variant in results: |
|
|
if (product['name'].upper() in variant['name'].upper() and |
|
|
variant['stock_found'] and |
|
|
variant['total_stock'] > 0 and |
|
|
not is_main_product(variant['name'])): |
|
|
has_stocked_variants = True |
|
|
break |
|
|
if has_stocked_variants: |
|
|
stocked_alternatives.append(product) |
|
|
|
|
|
|
|
|
if stocked_alternatives: |
|
|
|
|
|
def get_model_priority(product): |
|
|
name = product['name'].upper() |
|
|
if '2026' in name or 'GEN 3' in name: |
|
|
return 3 |
|
|
elif '2025' in name or 'GEN 2' in name: |
|
|
return 2 |
|
|
elif '2024' in name or 'GEN' in name: |
|
|
return 1 |
|
|
else: |
|
|
return 0 |
|
|
|
|
|
stocked_alternatives.sort(key=lambda x: get_model_priority(x), reverse=True) |
|
|
|
|
|
|
|
|
if stocked_alternatives: |
|
|
results = [stocked_alternatives[0]] + [r for r in results if r != stocked_alternatives[0]] |
|
|
|
|
|
|
|
|
main_products = [] |
|
|
variant_products = [] |
|
|
|
|
|
for product in results: |
|
|
if is_main_product(product['name']): |
|
|
main_products.append(product) |
|
|
else: |
|
|
variant_products.append(product) |
|
|
|
|
|
|
|
|
def enrich_main_product_with_variant_stock(main_product, all_results): |
|
|
"""Ana ürün stoku yoksa varyant stoklarını topla - TAM EŞLEŞTİRME""" |
|
|
if main_product['stock_found'] and main_product['total_stock'] > 0: |
|
|
return main_product |
|
|
|
|
|
|
|
|
main_name = main_product['name'].upper() |
|
|
related_variants = [] |
|
|
|
|
|
for product in all_results: |
|
|
product_name_upper = product['name'].upper() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
variant_matches = False |
|
|
if (product['stock_found'] and |
|
|
product['total_stock'] > 0 and |
|
|
not is_main_product(product['name'])): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if product_name_upper.startswith(main_name): |
|
|
remaining_after_main = product_name_upper[len(main_name):].strip() |
|
|
|
|
|
|
|
|
model_separators = ['GEN 3', 'GEN', '(2026)', '(2025)', '2026', '2025'] |
|
|
has_model_separator = any(sep in remaining_after_main for sep in model_separators) |
|
|
|
|
|
|
|
|
main_has_separators = any(sep in main_name for sep in model_separators) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if main_has_separators: |
|
|
|
|
|
|
|
|
if not has_model_separator: |
|
|
|
|
|
if remaining_after_main and any(indicator in remaining_after_main for indicator in [' - ', ' MOR', ' SIYAH', ' MAVI', ' XS', ' S ', ' M ', ' L ', ' XL']): |
|
|
variant_matches = True |
|
|
else: |
|
|
|
|
|
if not has_model_separator: |
|
|
|
|
|
if remaining_after_main and any(indicator in remaining_after_main for indicator in [' - ', ' MOR', ' SIYAH', ' MAVI', ' XS', ' S ', ' M ', ' L ', ' XL']): |
|
|
variant_matches = True |
|
|
|
|
|
if variant_matches: |
|
|
related_variants.append(product) |
|
|
|
|
|
if related_variants: |
|
|
|
|
|
combined_warehouses = {} |
|
|
total_stock = 0 |
|
|
|
|
|
for variant in related_variants: |
|
|
total_stock += variant['total_stock'] |
|
|
for wh in variant['warehouses']: |
|
|
wh_name = wh['name'] |
|
|
wh_stock = wh['stock'] |
|
|
if wh_name in combined_warehouses: |
|
|
combined_warehouses[wh_name] += wh_stock |
|
|
else: |
|
|
combined_warehouses[wh_name] = wh_stock |
|
|
|
|
|
|
|
|
main_product['stock_found'] = True |
|
|
main_product['total_stock'] = total_stock |
|
|
main_product['warehouses'] = [ |
|
|
{'name': name, 'stock': stock} |
|
|
for name, stock in combined_warehouses.items() |
|
|
if stock > 0 |
|
|
] |
|
|
|
|
|
return main_product |
|
|
|
|
|
|
|
|
if main_products: |
|
|
|
|
|
enriched_main_products = [] |
|
|
for main_product in main_products: |
|
|
enriched = enrich_main_product_with_variant_stock(main_product, results) |
|
|
enriched_main_products.append(enriched) |
|
|
|
|
|
|
|
|
query_words = normalize_turkish(query).upper().split() |
|
|
product_query_words = [w for w in query_words if w not in ['FIYAT', 'STOK', 'VAR', 'MI', 'RENK', 'BEDEN']] |
|
|
is_simple_product_query = len(product_query_words) <= 2 |
|
|
|
|
|
if is_simple_product_query: |
|
|
selected_products = enriched_main_products[:1] |
|
|
else: |
|
|
selected_products = enriched_main_products[:3] |
|
|
else: |
|
|
|
|
|
selected_products = variant_products[:3] |
|
|
|
|
|
formatted_results = [] |
|
|
for product in selected_products: |
|
|
|
|
|
show_variants = is_main_product(product['name']) |
|
|
formatted = format_product_result_whatsapp(product, show_variants_info=show_variants, all_results=results) |
|
|
formatted_results.append(formatted) |
|
|
|
|
|
return formatted_results |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
|
logging.basicConfig(level=logging.INFO) |
|
|
|
|
|
print("Combined API Test Başlıyor...") |
|
|
|
|
|
test_queries = ["marlin", "trek tool", "bisiklet", "madone"] |
|
|
|
|
|
for query in test_queries: |
|
|
print(f"\n🔍 Test: '{query}'") |
|
|
results = search_products_combined_api(query) |
|
|
|
|
|
if results: |
|
|
print(f"✅ {len(results)} sonuç bulundu") |
|
|
for i, product in enumerate(results[:2]): |
|
|
print(f"\n{i+1}. {product['name']}") |
|
|
print(f" Fiyat: {product['regular_price']} TL") |
|
|
print(f" Stok: {'✅' if product['stock_found'] else '❌'} ({product['total_stock']} adet)") |
|
|
else: |
|
|
print("❌ Sonuç bulunamadı") |