"""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 ayarla logger = logging.getLogger(__name__) def normalize_turkish(text): """Türkçe karakter normalizasyonu""" if not text: return "" # Önce uppercase yap text = text.upper() # Türkçe karakterleri normalize et 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) # Birden fazla boşluğu tek boşluğa çevir import re text = re.sub(r'\s+', ' ', text) return text.strip() # Cache configuration CACHE_DURATION = 3600 # 1 saat cache = { 'combined_api': {'data': None, 'time': 0} } def get_combined_api_data(): """Combined API'den veriyi çek ve cache'le""" current_time = time.time() # Cache kontrolü 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: # Yeni combined API'yi çağır url = 'https://video.trek-turkey.com/combined_trek_xml_api_v2.php' response = requests.get(url, timeout=30) if response.status_code == 200: # XML parse et root = ET.fromstring(response.content) # Cache'e kaydet cache['combined_api']['data'] = root cache['combined_api']['time'] = current_time # İstatistikleri logla 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: # Combined API verisini al root = get_combined_api_data() if not root: return [] # Stop words - arama skorunu etkilememesi gereken kelimeler stop_words = ['VAR', 'MI', 'MEVCUT', 'MU', 'STOK', 'STOGU', 'DURUMU', 'BULUNUYOR'] # Query'yi normalize et ve stop words'leri filtrele 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 = [] # Tüm ürünlerde ara for product in root.findall('product'): # Ürün bilgilerini al stock_code = product.find('stock_code') name = product.find('name') label = product.find('label') url = product.find('url') image_url = product.find('image_url') # Fiyat bilgileri 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 # Kategori bilgileri 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 stok bilgileri 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) # Depo bilgileri 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) }) # Arama kriteri kontrolü - Türkçe normalizasyon ile 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) + " " # Geliştirilmiş arama eşleştirme - stop words filtrelenmiş query_words kullan filtered_query_words = query_words # Zaten üstte filtrelenmiş name_normalized = normalize_turkish(name.text.upper()) if name is not None else "" # 1. Temel kelime eşleşmesi basic_matches = sum(1 for word in filtered_query_words if word in searchable_text) if basic_matches > 0: # 2. Tam eşleşme bonusu - tüm query kelimeleri ürün adında exact_bonus = 10 if all(word in name_normalized for word in filtered_query_words) else 0 # 3. Sıra bonus - kelimeler doğru sırada mı? sequence_bonus = 0 if len(filtered_query_words) >= 2: query_sequence = " ".join(filtered_query_words) if query_sequence in name_normalized: sequence_bonus = 5 # 4. Uzunluk penaltısı - çok uzun ürün adları düşük skor alsın length_penalty = max(0, len(name_normalized.split()) - len(filtered_query_words)) * -1 # Toplam skor 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) # Sonuçları match score'a göre sırala 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] # İlk 10 sonuç 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 = [] # Ürün adı result.append(f"🚲 **{name}**") # Fiyat bilgisi - Original Trek fiyat yuvarlama algoritması (app.py'dan) def format_price(price_str): if not price_str: return "" try: price_float = float(price_str) # Original Trek fiyat yuvarlama algoritması 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 # Türk Lirası formatı: 1.234,56 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") # Stok durumu - Varyant detayları ile if stock_found and total_stock > 0: # Hangi mağazalarda var + varyant bilgisi (ana ürünse) if warehouses: # Ana ürün ise varyant detaylarını da göster if show_variants_info and all_results and is_main_product(name): result.append("📦 **Mevcut varyantlar:**") # Bu ana ürünün varyantlarını bul 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'])): # Varyant isminden beden/renk çıkar 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) # Mağaza bazında varyantları listele for store, variants in variant_details.items(): result.append(f"• **{store}:** {', '.join(variants)}") # Genel bilgi result.append("📞 Diğer beden/renk teyidi için mağazaları arayın") else: # Normal stok bilgisi 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") # Ürün linki if url: result.append(f"🔗 [Ürün Detayları]({url})") # Ürün resmi (WhatsApp için) - Direkt URL WhatsApp'ta resmi gösterir if image_url and image_url.startswith('https://'): # WhatsApp'ta resmi direkt göstermek için sadece URL'i ver (link formatı değil) 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() # Varyant göstergeleri - beden, renk, yıl belirticileri variant_indicators = [ # Bedenler ' - XS', ' - S', ' - M', ' - L', ' - XL', ' - XXL', ' XS', ' S ', ' M ', ' L ', ' XL', ' XXL', # Renkler ' - SİYAH', ' - MAVİ', ' - KIRMIZI', ' - YEŞİL', ' - BEYAZ', ' - MOR', ' SİYAH', ' MAVİ', ' KIRMIZI', ' YEŞİL', ' BEYAZ', ' MOR', # İngilizce renkler ' - BLACK', ' - BLUE', ' - RED', ' - GREEN', ' - WHITE', ' - PURPLE', ' BLACK', ' BLUE', ' RED', ' GREEN', ' WHITE', ' PURPLE' ] # Eğer bu göstergelerden biri varsa varyant 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ı"] # Akıllı model geçişi - eski model stokta yoksa yeni modeli öner # Örn: "marlin 4" arandığında MARLIN 4 yoksa MARLIN 4 GEN 3'ü göster query_normalized = normalize_turkish(query.upper()) # Query'yi temizle - "var mı", "stok", "fiyat" gibi ekleri çıkar 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}'") # Genel ürün adı aranıyorsa (model numarası yok) if not any(year in clean_query for year in ['GEN', '2024', '2025', '2026', '(20']): # Stokta olan modelleri kontrol et stocked_alternatives = [] for product in results: name_normalized = normalize_turkish(product['name'].upper()) # Ana ürün adıyla eşleşiyor mu? (temizlenmiş query ile) if clean_query in name_normalized: # Ana ürün mü? if is_main_product(product['name']): # Stokta mı veya varyantları var mı? if product['stock_found'] and product['total_stock'] > 0: stocked_alternatives.append(product) else: # Ana ürün stokta yok, varyantları kontrol et 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) # Eğer alternatifler varsa, en yenisini seç (genelde GEN 3, 2026 vs.) if stocked_alternatives: # Model yılı/versiyonu olanları öncelikle 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) # En yeni modeli öncelikle kullan if stocked_alternatives: results = [stocked_alternatives[0]] + [r for r in results if r != stocked_alternatives[0]] # Önce ana ürünleri filtrele main_products = [] variant_products = [] for product in results: if is_main_product(product['name']): main_products.append(product) else: variant_products.append(product) # Ana ürünün stok bilgilerini varyantlarından topla 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 # Zaten stok bilgisi var # Bu ana ürünün varyantlarını bul - TAM EŞLEŞTIRME main_name = main_product['name'].upper() related_variants = [] for product in all_results: product_name_upper = product['name'].upper() # TAM EŞLEŞTIRME: varyant ana ürünle TAM BAŞLAMALI # Örn: "MARLIN 4 GEN 3 (2026) MOR - XS" → "MARLIN 4 GEN 3 (2026)" ile eşleşmeli # "MARLIN 4 SİYAH - XS" → "MARLIN 4" ile eşleşmeli variant_matches = False if (product['stock_found'] and product['total_stock'] > 0 and not is_main_product(product['name'])): # TAM ÜRÜN EŞLEŞTIRMESI - backup mantığıyla ters # Ana ürün "MARLIN 4" ise # Varyant "MARLIN 4 SİYAH - XS" EVET, "MARLIN 4 GEN 3 (...) MOR - XS" HAYIR # Ana ürün tam adıyla başlamalı, fazla kelime olmamalı if product_name_upper.startswith(main_name): remaining_after_main = product_name_upper[len(main_name):].strip() # Kalan kısımda GEN/2026 gibi model ayırıcıları varsa bu farklı ürün model_separators = ['GEN 3', 'GEN', '(2026)', '(2025)', '2026', '2025'] has_model_separator = any(sep in remaining_after_main for sep in model_separators) # Ana ürün model ayırıcısı içeriyorsa, varyant da içermeli main_has_separators = any(sep in main_name for sep in model_separators) # Ana ürün model separator içeriyorsa, varyant onun devamı olabilir # Ana ürün model separator içermiyorsa, varyant da içermemeli if main_has_separators: # Ana ürün GEN 3 (2026) gibi → varyant da GEN 3'ün devamı olmalı # Kalan kısımda model separator olmamalı (çünkü ana üründe zaten var) if not has_model_separator: # Varyant göstergeleri var mı? 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: # Ana ürün model separator içermiyor → varyant da içermemeli if not has_model_separator: # Varyant göstergeleri var mı? 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: # Varyant stoklarını topla 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 # Ana ürünü stok bilgileriyle zenginleştir 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 # Öncelikle ana ürünleri kullan, Ana ürün yoksa varyantları göster if main_products: # Ana ürünleri varyant stoklarıyla zenginleştir enriched_main_products = [] for main_product in main_products: enriched = enrich_main_product_with_variant_stock(main_product, results) enriched_main_products.append(enriched) # Basit sorgu (örn: "marlin 4") için sadece en iyi ana ürün 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] # Sadece en iyi ana ürün else: selected_products = enriched_main_products[:3] # Birden fazla ana ürün else: # Ana ürün yoksa varyantları göster selected_products = variant_products[:3] formatted_results = [] for product in selected_products: # Ana ürün ise varyant detaylarını göster 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 # Test fonksiyonu if __name__ == "__main__": # Test arama 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ı")