| | """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ı") |