BF-WAB / app.py
SamiKoen's picture
Upload 7 files
4fa4ebe verified
raw
history blame
57.9 kB
import os
import json
import requests
import xml.etree.ElementTree as ET
import warnings
import time
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
from fastapi import FastAPI, Request
from twilio.rest import Client
from twilio.twiml.messaging_response import MessagingResponse
# Yeni modüller - Basit sistem
from prompts import get_prompt_content_only
from whatsapp_renderer import extract_product_info_whatsapp
from whatsapp_passive_profiler import (
analyze_user_message, get_user_profile_summary, get_personalized_recommendations
)
# Import improved WhatsApp search for BF space
try:
from whatsapp_improved_chatbot import WhatsAppImprovedChatbot
USE_IMPROVED_SEARCH = True
except ImportError:
print("Improved WhatsApp chatbot not available, using basic search")
USE_IMPROVED_SEARCH = False
# LOGGING EN BAŞA EKLENDİ
import logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
warnings.simplefilter('ignore')
# API ayarları
API_URL = "https://api.openai.com/v1/chat/completions"
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
logger.info(f"OpenAI API Key var mı: {'Evet' if OPENAI_API_KEY else 'Hayır'}")
# Twilio WhatsApp ayarları
TWILIO_ACCOUNT_SID = os.getenv("TWILIO_ACCOUNT_SID")
TWILIO_AUTH_TOKEN = os.getenv("TWILIO_AUTH_TOKEN")
TWILIO_MESSAGING_SERVICE_SID = os.getenv("TWILIO_MESSAGING_SERVICE_SID")
TWILIO_WHATSAPP_NUMBER = "whatsapp:+905332047254"
logger.info(f"Twilio SID var mı: {'Evet' if TWILIO_ACCOUNT_SID else 'Hayır'}")
logger.info(f"Twilio Auth Token var mı: {'Evet' if TWILIO_AUTH_TOKEN else 'Hayır'}")
logger.info(f"Messaging Service SID var mı: {'Evet' if TWILIO_MESSAGING_SERVICE_SID else 'Hayır'}")
if not TWILIO_ACCOUNT_SID or not TWILIO_AUTH_TOKEN:
logger.error("❌ Twilio bilgileri eksik!")
twilio_client = None
else:
try:
twilio_client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
logger.info("✅ Twilio client başarıyla oluşturuldu!")
except Exception as e:
logger.error(f"❌ Twilio client hatası: {e}")
twilio_client = None
# Mağaza stok bilgilerini çekme fonksiyonu
def get_warehouse_stock(product_name):
"""B2B API'den mağaza stok bilgilerini çek - İyileştirilmiş versiyon"""
try:
import re
warehouse_url = 'https://video.trek-turkey.com/bizimhesap-warehouse-xml.php'
response = requests.get(warehouse_url, verify=False, timeout=15)
if response.status_code != 200:
return None
root = ET.fromstring(response.content)
# Turkish character normalization function
turkish_map = {'ı': 'i', 'ğ': 'g', 'ü': 'u', 'ş': 's', 'ö': 'o', 'ç': 'c', 'İ': 'i', 'I': 'i'}
def normalize_turkish(text):
import unicodedata
text = unicodedata.normalize('NFD', text)
text = ''.join(char for char in text if unicodedata.category(char) != 'Mn')
for tr_char, en_char in turkish_map.items():
text = text.replace(tr_char, en_char)
return text
# Normalize search product name
search_name = normalize_turkish(product_name.lower().strip())
search_name = search_name.replace('(2026)', '').replace('(2025)', '').replace(' gen 3', '').replace(' gen', '').strip()
search_words = search_name.split()
best_matches = []
exact_matches = []
variant_matches = []
candidates = []
# Check if this is a size/color specific query (like "M Turuncu")
is_size_color_query = (len(search_words) <= 3 and
any(word in ['s', 'm', 'l', 'xl', 'xs', 'small', 'medium', 'large',
'turuncu', 'siyah', 'beyaz', 'mavi', 'kirmizi', 'yesil',
'orange', 'black', 'white', 'blue', 'red', 'green']
for word in search_words))
# İlk geçiş: Variant alanında beden/renk araması
if is_size_color_query:
for product in root.findall('Product'):
product_name_elem = product.find('ProductName')
variant_elem = product.find('Variant')
if product_name_elem is not None and product_name_elem.text:
xml_product_name = product_name_elem.text.strip()
# Variant field check
if variant_elem is not None and variant_elem.text:
variant_text = normalize_turkish(variant_elem.text.lower().replace('-', ' '))
# Check if all search words are in variant field
if all(word in variant_text for word in search_words):
variant_matches.append((product, xml_product_name, variant_text))
if variant_matches:
candidates = variant_matches
else:
# Fallback to normal product name search
is_size_color_query = False
# İkinci geçiş: Normal ürün adı tam eşleşmeleri (variant match yoksa)
if not is_size_color_query or not candidates:
for product in root.findall('Product'):
product_name_elem = product.find('ProductName')
if product_name_elem is not None and product_name_elem.text:
xml_product_name = product_name_elem.text.strip()
normalized_xml = normalize_turkish(xml_product_name.lower())
normalized_xml = normalized_xml.replace('(2026)', '').replace('(2025)', '').replace(' gen 3', '').replace(' gen', '').strip()
xml_words = normalized_xml.split()
# Tam eşleşme kontrolü - ilk iki kelime tam aynı olmalı
if len(search_words) >= 2 and len(xml_words) >= 2:
search_key = f"{search_words[0]} {search_words[1]}"
xml_key = f"{xml_words[0]} {xml_words[1]}"
if search_key == xml_key:
exact_matches.append((product, xml_product_name, normalized_xml))
# Eğer variant match varsa onu kullan, yoksa exact matches kullan
if not candidates:
candidates = exact_matches if exact_matches else []
# Eğer hala bir match yoksa, fuzzy matching yap
if not candidates:
for product in root.findall('Product'):
product_name_elem = product.find('ProductName')
if product_name_elem is not None and product_name_elem.text:
xml_product_name = product_name_elem.text.strip()
normalized_xml = normalize_turkish(xml_product_name.lower())
normalized_xml = normalized_xml.replace('(2026)', '').replace('(2025)', '').replace(' gen 3', '').replace(' gen', '').strip()
xml_words = normalized_xml.split()
# Ortak kelime sayısını hesapla
common_words = set(search_words) & set(xml_words)
# En az 2 ortak kelime olmalı VE ilk kelime aynı olmalı (marka kontrolü)
if (len(common_words) >= 2 and
len(search_words) > 0 and len(xml_words) > 0 and
search_words[0] == xml_words[0]):
best_matches.append((product, xml_product_name, normalized_xml, len(common_words)))
# En çok ortak kelimeye sahip olanları seç
if best_matches:
max_common = max(match[3] for match in best_matches)
candidates = [(match[0], match[1], match[2]) for match in best_matches if match[3] == max_common]
# Stok bilgilerini topla ve tekrarları önle
warehouse_stock_map = {} # warehouse_name -> total_stock
for product, xml_name, _ in candidates:
warehouses = product.find('Warehouses')
if warehouses is not None:
for warehouse in warehouses.findall('Warehouse'):
name_elem = warehouse.find('Name')
stock_elem = warehouse.find('Stock')
if name_elem is not None and stock_elem is not None:
warehouse_name = name_elem.text if name_elem.text else "Bilinmeyen"
try:
stock_count = int(stock_elem.text) if stock_elem.text else 0
if stock_count > 0:
# Aynı mağaza için stokları topla
if warehouse_name in warehouse_stock_map:
warehouse_stock_map[warehouse_name] += stock_count
else:
warehouse_stock_map[warehouse_name] = stock_count
except (ValueError, TypeError):
pass
if warehouse_stock_map:
# Mağaza stoklarını liste halinde döndür
all_warehouse_info = []
for warehouse_name, total_stock in warehouse_stock_map.items():
all_warehouse_info.append(f"{warehouse_name}: {total_stock} adet")
return all_warehouse_info
else:
return ["Hiçbir mağazada stokta bulunmuyor"]
except Exception as e:
print(f"Mağaza stok bilgisi çekme hatası: {e}")
return None
# Trek bisiklet ürünlerini çekme - DÜZELTİLMİŞ VERSİYON
try:
print("🔍 XML DEBUG TEST BAŞLADI")
print("========================")
url = 'https://www.trekbisiklet.com.tr/output/8582384479'
print(f"📡 URL: {url}")
print("📥 HTTP isteği gönderiliyor...")
response = requests.get(url, verify=False, timeout=10)
print(f"✅ HTTP Status: {response.status_code}")
print(f"📏 Content Length: {len(response.content)} bytes")
# İlk 500 karakteri göster
content_preview = response.content[:500].decode('utf-8', errors='ignore')
print(f"📄 İlk 500 karakter:")
print(content_preview)
print("...")
print("\n🔄 XML parsing...")
root = ET.fromstring(response.content)
print(f"✅ XML root tag: {root.tag}")
all_items = root.findall('item')
print(f"📦 Toplam item sayısı: {len(all_items)}")
# İlk birkaç item'ı incele
print(f"\n🔍 İlk 3 item analizi:")
for i, item in enumerate(all_items[:3]):
print(f"\n--- ITEM {i+1} ---")
rootlabel = item.find('rootlabel')
if rootlabel is not None and rootlabel.text:
print(f"📛 Ürün: {rootlabel.text}")
else:
print("❌ rootlabel bulunamadı")
stock = item.find('stockAmount')
if stock is not None and stock.text:
print(f"📦 Stok: {stock.text}")
else:
print("❌ stockAmount bulunamadı")
price = item.find('priceTaxWithCur')
if price is not None and price.text:
print(f"💰 Fiyat: {price.text}")
else:
print("❌ priceTaxWithCur bulunamadı")
# Marlin arama testi
print(f"\n🎯 MARLIN ARAMA TESİ:")
marlin_count = 0
products = []
for item in all_items:
# Değişkenleri önceden tanımla
stock_number = 0
stock_amount = "stokta değil"
price = ""
price_eft = ""
product_link = ""
rootlabel = item.find('rootlabel')
if rootlabel is None or not rootlabel.text:
continue
full_name = rootlabel.text.strip()
name_words = full_name.lower().split()
name = name_words[0] if name_words else "unknown"
# STOK KONTROLÜ - SAYISAL KARŞILAŞTIRMA
stock_element = item.find('stockAmount')
if stock_element is not None and stock_element.text:
try:
stock_number = int(stock_element.text.strip())
stock_amount = "stokta" if stock_number > 0 else "stokta değil"
except (ValueError, TypeError):
stock_number = 0
stock_amount = "stokta değil"
# Marlin kontrolü
if 'marlin' in full_name.lower():
marlin_count += 1
print(f"🎯 MARLIN BULUNDU #{marlin_count}: {full_name}")
print(f" 📦 Stok: {stock_number} adet")
print(f" 📊 Durum: {stock_amount}")
# Stokta olan ürünler için fiyat bilgilerini al
if stock_amount == "stokta":
# Normal fiyat
price_element = item.find('priceTaxWithCur')
price_str = price_element.text if price_element is not None and price_element.text else "0"
# Kampanya fiyatı
price_rebate_element = item.find('priceRebateWithTax')
price_rebate_str = price_rebate_element.text if price_rebate_element is not None and price_rebate_element.text else ""
# Kampanya fiyatı varsa onu kullan, yoksa normal fiyatı kullan
final_price_str = price_str
if price_rebate_str:
try:
normal_price = float(price_str)
rebate_price = float(price_rebate_str)
# Kampanya fiyatı normal fiyattan farklı ve düşükse kullan
if rebate_price < normal_price:
final_price_str = price_rebate_str
except (ValueError, TypeError):
final_price_str = price_str
# EFT fiyatı
price_eft_element = item.find('priceEft')
price_eft_str = price_eft_element.text if price_eft_element is not None and price_eft_element.text else ""
# Ürün linki
link_element = item.find('productLink')
product_link = link_element.text if link_element is not None and link_element.text else ""
# Fiyat formatting (kampanya fiyatı veya normal fiyat)
try:
price_float = float(final_price_str)
if price_float > 200000:
price = str(round(price_float / 5000) * 5000)
elif price_float > 30000:
price = str(round(price_float / 1000) * 1000)
elif price_float > 10000:
price = str(round(price_float / 100) * 100)
else:
price = str(round(price_float / 10) * 10)
except (ValueError, TypeError):
price = final_price_str
# EFT fiyat formatting
if price_eft_str:
try:
price_eft_float = float(price_eft_str)
if price_eft_float > 200000:
price_eft = str(round(price_eft_float / 5000) * 5000)
elif price_eft_float > 30000:
price_eft = str(round(price_eft_float / 1000) * 1000)
elif price_eft_float > 10000:
price_eft = str(round(price_eft_float / 100) * 100)
else:
price_eft = str(round(price_eft_float / 10) * 10)
except (ValueError, TypeError):
price_eft = price_eft_str
else:
try:
price_eft_float = float(price_str)
price_eft = str(round(price_eft_float * 0.975 / 10) * 10)
except:
price_eft = ""
# Ürün bilgilerini tuple olarak oluştur
item_info = (stock_amount, price, product_link, price_eft, str(stock_number))
products.append((name, item_info, full_name))
print(f"\n📊 SONUÇ:")
print(f" Toplam ürün: {len(all_items)}")
print(f" Marlin ürünü: {marlin_count}")
print(f" Products listesi: {len(products)} ürün")
# Initialize improved WhatsApp chatbot for BF space
global improved_whatsapp_bot
improved_whatsapp_bot = None
if USE_IMPROVED_SEARCH:
try:
improved_whatsapp_bot = WhatsAppImprovedChatbot(products)
print("✅ BF Space: Improved WhatsApp product search initialized successfully")
except Exception as e:
print(f"❌ BF Space: Failed to initialize improved WhatsApp search: {e}")
USE_IMPROVED_SEARCH = False
improved_whatsapp_bot = None
# Enhanced features kaldırıldı - GPT-4 doğal dil anlama kullanacak
print("✅ Basit sistem aktif - GPT-4 doğal dil anlama")
if marlin_count == 0:
print("❌ XML'de hiç Marlin ürünü bulunamadı!")
print(" Muhtemelen XML'de sadece aksesuar/yedek parça var.")
else:
print("✅ Marlin ürünleri XML'de mevcut")
# Marlin stok raporu
marlin_products = [p for p in products if 'marlin' in p[2].lower()]
marlin_in_stock = [p for p in marlin_products if p[1][0] == "stokta"]
marlin_out_of_stock = [p for p in marlin_products if p[1][0] == "stokta değil"]
print(f"\n📊 MARLIN STOK RAPORU:")
print(f" 📦 Toplam Marlin modeli: {len(marlin_products)}")
print(f" ✅ Stokta olan: {len(marlin_in_stock)}")
print(f" ❌ Stokta olmayan: {len(marlin_out_of_stock)}")
if marlin_in_stock:
print("\n✅ STOKTA OLAN MARLIN MODELLERİ:")
for product in marlin_in_stock:
print(f" • {product[2]} - {product[1][1]} TL ({product[1][4]} adet)")
if marlin_out_of_stock:
print("\n❌ STOKTA OLMAYAN MARLIN MODELLERİ:")
for product in marlin_out_of_stock:
print(f" • {product[2]}")
except Exception as e:
print(f"❌ Ürün yükleme hatası: {e}")
import traceback
traceback.print_exc()
products = []
# ===============================
# STOK API ENTEGRASYONU
# ===============================
STOCK_API_BASE = "https://video.trek-turkey.com/bizimhesap-proxy.php"
# Stock cache (5 dakikalık cache)
stock_cache = {}
CACHE_DURATION = 300 # 5 dakika (saniye cinsinden)
def normalize_turkish(text):
"""Türkçe karakterleri normalize et"""
if not text:
return ""
replacements = {
'ı': 'i', 'İ': 'i', 'ş': 's', 'Ş': 's',
'ğ': 'g', 'Ğ': 'g', 'ü': 'u', 'Ü': 'u',
'ö': 'o', 'Ö': 'o', 'ç': 'c', 'Ç': 'c'
}
text = text.lower()
for tr_char, eng_char in replacements.items():
text = text.replace(tr_char, eng_char)
return text
def fetch_warehouse_inventory(warehouse, product_name, search_terms):
"""Tek bir mağazanın stok bilgisini al"""
try:
warehouse_id = warehouse['id']
warehouse_name = warehouse['title']
# DSW'yi ayrı tut (gelecek stok için)
is_dsw = 'DSW' in warehouse_name or 'ÖN SİPARİŞ' in warehouse_name.upper()
# Mağaza stoklarını al
inventory_url = f"{STOCK_API_BASE}?action=inventory&warehouse={warehouse_id}&endpoint=inventory/{warehouse_id}"
inventory_response = requests.get(inventory_url, timeout=3, verify=False)
if inventory_response.status_code != 200:
return None
inventory_data = inventory_response.json()
# API yanıtını kontrol et
if 'data' not in inventory_data or 'inventory' not in inventory_data['data']:
return None
products = inventory_data['data']['inventory']
# Beden terimleri kontrolü
size_terms = ['xs', 's', 'm', 'ml', 'l', 'xl', 'xxl', '2xl', '3xl', 'small', 'medium', 'large']
size_numbers = ['44', '46', '48', '50', '52', '54', '56', '58', '60']
# Arama terimlerinde beden var mı kontrol et
has_size_query = False
size_query = None
for term in search_terms:
if term in size_terms or term in size_numbers:
has_size_query = True
size_query = term
break
# Eğer sadece beden sorgusu varsa (ör: "m", "xl")
is_only_size_query = len(search_terms) == 1 and has_size_query
# Ürünü ara
warehouse_variants = []
dsw_stock_count = 0
for product in products:
product_title = normalize_turkish(product.get('title', '')).lower()
original_title = product.get('title', '')
# Eğer sadece beden sorgusu ise
if is_only_size_query:
# Beden terimini ürün başlığında ara (parantez içinde veya dışında)
if size_query in product_title.split() or f'({size_query})' in product_title or f' {size_query} ' in product_title or product_title.endswith(f' {size_query}'):
qty = int(product.get('qty', 0))
stock = int(product.get('stock', 0))
actual_stock = max(qty, stock)
if actual_stock > 0:
if is_dsw:
dsw_stock_count += actual_stock
continue
warehouse_variants.append(f"{original_title}: ✓ Stokta")
else:
# Normal ürün araması - tüm terimler eşleşmeli
# Ama beden terimi varsa özel kontrol yap
if has_size_query:
# Beden hariç diğer terimleri kontrol et
non_size_terms = [t for t in search_terms if t != size_query]
product_matches = all(term in product_title for term in non_size_terms)
# Beden kontrolü - daha esnek
size_matches = size_query in product_title.split() or f'({size_query})' in product_title or f' {size_query} ' in product_title or product_title.endswith(f' {size_query}')
if product_matches and size_matches:
qty = int(product.get('qty', 0))
stock = int(product.get('stock', 0))
actual_stock = max(qty, stock)
if actual_stock > 0:
if is_dsw:
dsw_stock_count += actual_stock
continue
# Varyant bilgisini göster
variant_info = original_title
possible_names = [
product_name.upper(),
product_name.lower(),
product_name.title(),
product_name.upper().replace('I', 'İ'),
product_name.upper().replace('İ', 'I'),
]
if 'fx sport' in product_name.lower():
possible_names.extend(['FX Sport AL 3', 'FX SPORT AL 3', 'Fx Sport Al 3'])
for possible_name in possible_names:
variant_info = variant_info.replace(possible_name, '').strip()
variant_info = ' '.join(variant_info.split())
if variant_info and variant_info != original_title:
warehouse_variants.append(f"{variant_info}: ✓ Stokta")
else:
warehouse_variants.append(f"{original_title}: ✓ Stokta")
else:
# Beden sorgusu yoksa normal kontrol
if all(term in product_title for term in search_terms):
qty = int(product.get('qty', 0))
stock = int(product.get('stock', 0))
actual_stock = max(qty, stock)
if actual_stock > 0:
if is_dsw:
dsw_stock_count += actual_stock
continue
variant_info = original_title
possible_names = [
product_name.upper(),
product_name.lower(),
product_name.title(),
product_name.upper().replace('I', 'İ'),
product_name.upper().replace('İ', 'I'),
]
if 'fx sport' in product_name.lower():
possible_names.extend(['FX Sport AL 3', 'FX SPORT AL 3', 'Fx Sport Al 3'])
for possible_name in possible_names:
variant_info = variant_info.replace(possible_name, '').strip()
variant_info = ' '.join(variant_info.split())
if variant_info and variant_info != original_title:
warehouse_variants.append(f"{variant_info}: ✓ Stokta")
else:
warehouse_variants.append(f"{original_title}: ✓ Stokta")
# Sonuç döndür
if warehouse_variants and not is_dsw:
return {'warehouse': warehouse_name, 'variants': warehouse_variants, 'is_dsw': False}
elif dsw_stock_count > 0:
return {'dsw_stock': dsw_stock_count, 'is_dsw': True}
return None
except Exception:
return None
def get_realtime_stock_parallel(product_name):
"""API'den gerçek zamanlı stok bilgisini çek - Paralel versiyon with cache"""
try:
# Cache kontrolü
cache_key = normalize_turkish(product_name).lower()
current_time = time.time()
if cache_key in stock_cache:
cached_data, cached_time = stock_cache[cache_key]
# Cache hala geçerli mi?
if current_time - cached_time < CACHE_DURATION:
logger.info(f"Cache'den döndürülüyor: {product_name}")
return cached_data
# Önce mağaza listesini al
warehouses_url = f"{STOCK_API_BASE}?action=warehouses&endpoint=warehouses"
warehouses_response = requests.get(warehouses_url, timeout=3, verify=False)
if warehouses_response.status_code != 200:
logger.error(f"Mağaza listesi alınamadı: {warehouses_response.status_code}")
return None
warehouses_data = warehouses_response.json()
# API yanıtını kontrol et
if 'data' not in warehouses_data or 'warehouses' not in warehouses_data['data']:
logger.error("Mağaza verisi bulunamadı")
return None
warehouses = warehouses_data['data']['warehouses']
# Ürün adını normalize et
search_terms = normalize_turkish(product_name).lower().split()
logger.info(f"Aranan ürün: {product_name} -> {search_terms}")
stock_info = {}
total_dsw_stock = 0
total_stock = 0
# Paralel olarak tüm mağazaları sorgula
with ThreadPoolExecutor(max_workers=10) as executor:
# Tüm mağazalar için görev oluştur
futures = {
executor.submit(fetch_warehouse_inventory, warehouse, product_name, search_terms): warehouse
for warehouse in warehouses
}
# Sonuçları topla
for future in as_completed(futures):
result = future.result()
if result:
if result.get('is_dsw'):
total_dsw_stock += result.get('dsw_stock', 0)
else:
warehouse_name = result['warehouse']
stock_info[warehouse_name] = result['variants']
total_stock += 1 # En az bir mağazada var
# Sonucu oluştur
if not stock_info:
# Mağazada yok ama DSW'de varsa
if total_dsw_stock > 0:
result = f"{product_name}: Şu anda mağazalarda stokta yok, ancak yakında gelecek. Ön sipariş verebilirsiniz."
else:
result = f"{product_name}: Şu anda hiçbir mağazada stokta bulunmuyor."
else:
# Minimal prompt oluştur - varyant detaylarıyla
prompt_lines = [f"{product_name} stok durumu:"]
for warehouse, variants in stock_info.items():
if isinstance(variants, list):
prompt_lines.append(f"- {warehouse}:")
for variant in variants:
prompt_lines.append(f" • {variant}")
else:
prompt_lines.append(f"- {warehouse}: {variants}")
# Güvenlik: Toplam adet bilgisi gösterme
if total_stock > 0:
prompt_lines.append(f"✓ Ürün stokta mevcut")
result = "\n".join(prompt_lines)
# Sonucu cache'e kaydet
stock_cache[cache_key] = (result, current_time)
return result
except Exception as e:
logger.error(f"API hatası: {e}")
return None
def is_stock_query(message):
"""Mesajın stok sorgusu olup olmadığını kontrol et"""
stock_keywords = ['stok', 'stock', 'var mı', 'mevcut mu', 'kaç adet',
'kaç tane', 'bulunuyor mu', 'hangi mağaza',
'nerede var', 'beden', 'numara']
message_lower = message.lower()
return any(keyword in message_lower for keyword in stock_keywords)
# Sistem mesajları - Modüler prompts'tan yükle
def get_system_messages():
return get_prompt_content_only() # prompts.py'dan yükle
# ===============================
# SOHBET HAFIZASI SİSTEMİ
# ===============================
# Sohbet hafızası için basit bir dictionary
conversation_memory = {}
def get_conversation_context(phone_number):
"""Kullanıcının sohbet geçmişini getir"""
if phone_number not in conversation_memory:
conversation_memory[phone_number] = {
"messages": [],
"current_category": None,
"last_activity": None
}
return conversation_memory[phone_number]
def add_to_conversation(phone_number, user_message, ai_response):
"""Sohbet geçmişine ekle"""
import datetime
context = get_conversation_context(phone_number)
context["last_activity"] = datetime.datetime.now()
context["messages"].append({
"user": user_message,
"ai": ai_response,
"timestamp": datetime.datetime.now()
})
# Sadece son 10 mesajı tut
if len(context["messages"]) > 10:
context["messages"] = context["messages"][-10:]
detect_category(phone_number, user_message, ai_response)
def detect_category(phone_number, user_message, ai_response):
"""Konuşulan kategoriyi tespit et"""
context = get_conversation_context(phone_number)
categories = {
"marlin": ["marlin", "marlin+"],
"madone": ["madone"],
"emonda": ["emonda", "émonda"],
"domane": ["domane"],
"checkpoint": ["checkpoint"],
"fuel": ["fuel", "fuel ex", "fuel exe"],
"procaliber": ["procaliber"],
"supercaliber": ["supercaliber"],
"fx": ["fx"],
"ds": ["ds", "dual sport"],
"powerfly": ["powerfly"],
"rail": ["rail"],
"verve": ["verve"],
"townie": ["townie"]
}
user_lower = user_message.lower()
for category, keywords in categories.items():
for keyword in keywords:
if keyword in user_lower:
context["current_category"] = category
print(f"🎯 Kategori tespit edildi: {category} ({phone_number})")
return category
return context.get("current_category")
def build_context_messages(phone_number, current_message):
"""Sohbet geçmişi ile sistem mesajlarını oluştur"""
context = get_conversation_context(phone_number)
system_messages = get_system_messages()
# Mevcut kategori varsa, sistem mesajına ekle
if context.get("current_category"):
category_msg = f"Kullanıcı şu anda {context['current_category'].upper()} kategorisi hakkında konuşuyor. Tüm cevaplarını bu kategori bağlamında ver. Kullanıcı yeni bir kategori belirtmediği sürece {context['current_category']} hakkında bilgi vermek istediğini varsay."
system_messages.append({"role": "system", "content": category_msg})
# Son 3 mesaj alışverişini ekle
recent_messages = context["messages"][-3:] if context["messages"] else []
all_messages = system_messages.copy()
# Geçmiş mesajları ekle
for msg in recent_messages:
all_messages.append({"role": "user", "content": msg["user"]})
all_messages.append({"role": "assistant", "content": msg["ai"]})
# Mevcut mesajı ekle
all_messages.append({"role": "user", "content": current_message})
return all_messages
def process_whatsapp_message_with_memory(user_message, phone_number):
"""Hafızalı WhatsApp mesaj işleme"""
try:
# 🧠 Pasif profil analizi - kullanıcı mesajını analiz et
profile_analysis = analyze_user_message(phone_number, user_message)
logger.info(f"📊 Profil analizi: {phone_number} -> {profile_analysis}")
# 🎯 Kişiselleştirilmiş öneriler kontrolü
if any(keyword in user_message.lower() for keyword in ["öneri", "öner", "tavsiye", "ne önerirsin"]):
personalized = get_personalized_recommendations(phone_number, products)
if personalized.get("personalized") and personalized.get("recommendations"):
# Kullanıcı profiline göre özelleştirilmiş cevap hazırla
profile_summary = get_user_profile_summary(phone_number)
custom_response = create_personalized_response(personalized, profile_summary)
return extract_product_info_whatsapp(custom_response)
# Enhanced features kaldırıldı - GPT-4 doğal dil anlama kullanacak
# Sohbet geçmişi ile sistem mesajlarını oluştur
messages = build_context_messages(phone_number, user_message)
# 🎯 Profil bilgilerini sistem mesajlarına ekle
profile_summary = get_user_profile_summary(phone_number)
if profile_summary.get("exists") and profile_summary.get("confidence", 0) > 0.3:
profile_context = create_profile_context_message(profile_summary)
messages.append({"role": "system", "content": profile_context})
# 🔍 BF Space: Use improved product search if available
product_found_improved = False
if USE_IMPROVED_SEARCH and improved_whatsapp_bot:
try:
product_result = improved_whatsapp_bot.process_message(user_message)
if product_result['is_product_query'] and product_result['response']:
# Check if user is asking about specific warehouse/store location
if any(keyword in user_message.lower() for keyword in ['mağaza', 'mağazada', 'nerede', 'hangi mağaza', 'şube']):
# First, always search for products using improved search
# This will find products even with partial/typo names
warehouse_info_parts = []
# Use the response text from improved search to extract product names
if product_result['response']:
# Extract product names from the response
import re
# Look for product names in bold (between * markers)
product_names = re.findall(r'\*([^*]+)\*', product_result['response'])
if product_names:
for product_name in product_names[:3]: # Max 3 products
# Clean up the product name
product_name = product_name.strip()
# Remove numbering like "1." "2." from the beginning
import re
product_name = re.sub(r'^\d+\.\s*', '', product_name)
# Skip status indicators
if product_name in ['Stokta mevcut', 'Stokta yok', 'Fiyat:', 'Kampanya:', 'İndirim:', 'Birden fazla ürün buldum:']:
continue
warehouse_stock = get_warehouse_stock(product_name)
if warehouse_stock and warehouse_stock != ['Ürün bulunamadı'] and warehouse_stock != ['Hiçbir mağazada stokta bulunmuyor']:
warehouse_info_parts.append(f"{product_name} mağaza stogu:")
warehouse_info_parts.extend(warehouse_stock)
warehouse_info_parts.append("")
break # Found warehouse info, stop searching
# If still no warehouse info, use products_found as backup
if not warehouse_info_parts and product_result['products_found']:
for product in product_result['products_found'][:2]:
product_name = product[2] # Full product name
warehouse_stock = get_warehouse_stock(product_name)
if warehouse_stock and warehouse_stock != ['Ürün bulunamadı'] and warehouse_stock != ['Hiçbir mağazada stokta bulunmuyor']:
warehouse_info_parts.append(f"{product_name} mağaza stogu:")
warehouse_info_parts.extend(warehouse_stock)
warehouse_info_parts.append("")
break
if warehouse_info_parts:
warehouse_response = "\n".join(warehouse_info_parts)
messages.append({
"role": "system",
"content": f"MAĞAZA STOK BİLGİSİ (BF Space):\n{warehouse_response}\n\nSADECE bu bilgileri kullanarak kullanıcıya yardımcı ol."
})
product_found_improved = True
logger.info("✅ BF Space: Warehouse stock info used")
if not product_found_improved:
# Use improved search response directly
messages.append({
"role": "system",
"content": f"ÜRÜN BİLGİSİ (BF Space):\n{product_result['response']}\n\nSADECE bu bilgileri kullanarak kullanıcıya yardımcı ol. Bu bilgiler dışında ek bilgi ekleme."
})
product_found_improved = True
logger.info("✅ BF Space: Improved product search used")
except Exception as e:
logger.error(f"❌ BF Space: Improved search error: {e}")
# Fallback to basic search if improved search didn't work
if not product_found_improved:
# Ürün bilgilerini kontrol et ve ekle (basic search)
input_words = user_message.lower().split()
for product_info in products:
if product_info[0] in input_words:
if product_info[1][0] == "stokta":
normal_price = f"Fiyat: {product_info[1][1]} TL"
if product_info[1][3]:
eft_price = f"Havale: {product_info[1][3]} TL"
price_info = f"{normal_price}, {eft_price}"
else:
price_info = normal_price
new_msg = f"{product_info[2]} {product_info[1][0]} - {price_info}"
else:
new_msg = f"{product_info[2]} {product_info[1][0]}"
messages.append({"role": "system", "content": new_msg})
break # Avoid duplicates
if not OPENAI_API_KEY:
return "OpenAI API anahtarı eksik. Lütfen environment variables'ları kontrol edin."
payload = {
"model": "gpt-5-chat-latest",
"messages": messages,
"temperature": 0,
"max_tokens": 800,
"stream": False,
}
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {OPENAI_API_KEY}"
}
response = requests.post(API_URL, headers=headers, json=payload)
if response.status_code == 200:
result = response.json()
ai_response = result['choices'][0]['message']['content']
# WhatsApp için resim URL'lerini formatla
formatted_response = extract_product_info_whatsapp(ai_response)
# Sohbet geçmişine ekle
add_to_conversation(phone_number, user_message, formatted_response)
return formatted_response
else:
print(f"OpenAI API Error: {response.status_code} - {response.text}")
return f"API hatası: {response.status_code}. Lütfen daha sonra tekrar deneyin."
except Exception as e:
print(f"❌ WhatsApp mesaj işleme hatası: {e}")
return "Teknik bir sorun oluştu. Lütfen daha sonra tekrar deneyin."
def create_profile_context_message(profile_summary):
"""Profil bilgilerini sistem mesajına çevir"""
context_parts = []
preferences = profile_summary.get("preferences", {})
behavior = profile_summary.get("behavior", {})
# Bütçe bilgisi
if preferences.get("budget_min") and preferences.get("budget_max"):
budget_min = preferences["budget_min"]
budget_max = preferences["budget_max"]
context_parts.append(f"Kullanıcının bütçesi: {budget_min:,}-{budget_max:,} TL")
# Kategori tercihleri
if preferences.get("categories"):
categories = ", ".join(preferences["categories"])
context_parts.append(f"İlgilendiği kategoriler: {categories}")
# Kullanım amacı
if preferences.get("usage_purpose"):
purposes = ", ".join(preferences["usage_purpose"])
context_parts.append(f"Kullanım amacı: {purposes}")
# Davranış kalıpları
if behavior.get("price_sensitive"):
context_parts.append("Fiyata duyarlı bir kullanıcı")
if behavior.get("tech_interested"):
context_parts.append("Teknik detaylarla ilgilenen bir kullanıcı")
if behavior.get("comparison_lover"):
context_parts.append("Karşılaştırma yapmayı seven bir kullanıcı")
# Etkileşim stili
interaction_style = profile_summary.get("interaction_style", "balanced")
style_descriptions = {
"analytical": "Detaylı ve analitik bilgi bekleyen",
"budget_conscious": "Bütçe odaklı ve ekonomik seçenekleri arayan",
"technical": "Teknik özellikler ve spesifikasyonlarla ilgilenen",
"decisive": "Hızlı karar veren ve özet bilgi isteyen",
"balanced": "Dengeli yaklaşım sergileyen"
}
context_parts.append(f"{style_descriptions.get(interaction_style, 'balanced')} bir kullanıcı")
if context_parts:
return f"Kullanıcı profili: {'. '.join(context_parts)}. Bu bilgileri göz önünde bulundurarak cevap ver."
return ""
def create_personalized_response(personalized_data, profile_summary):
"""Kişiselleştirilmiş öneri cevabı oluştur"""
response_parts = []
# Kullanıcı stiline göre selamlama
interaction_style = profile_summary.get("interaction_style", "balanced")
if interaction_style == "analytical":
response_parts.append("🔍 Profilinizi analiz ederek sizin için en uygun seçenekleri belirledim:")
elif interaction_style == "budget_conscious":
response_parts.append("💰 Bütçenize uygun en iyi seçenekleri hazırladım:")
elif interaction_style == "technical":
response_parts.append("⚙️ Teknik tercihlerinize göre önerilerim:")
else:
response_parts.append("🎯 Size özel seçtiklerim:")
# Önerileri listele
recommendations = personalized_data.get("recommendations", [])[:3] # İlk 3 öneri
if recommendations:
response_parts.append("\n")
for i, product in enumerate(recommendations, 1):
name, item_info, full_name = product
price = item_info[1] if len(item_info) > 1 else "Fiyat yok"
response_parts.append(f"**{i}. {full_name}**")
response_parts.append(f"💰 Fiyat: {price} TL")
response_parts.append("")
# Profil bazlı açıklama
preferences = profile_summary.get("preferences", {})
if preferences.get("categories"):
category = preferences["categories"][0]
response_parts.append(f"Bu öneriler {category} kategorisindeki ilginizi ve tercihlerinizi dikkate alarak hazırlandı.")
return "\n".join(response_parts)
def split_long_message(message, max_length=1600):
"""Uzun mesajları WhatsApp için uygun parçalara böler"""
if len(message) <= max_length:
return [message]
parts = []
remaining = message
while len(remaining) > max_length:
cut_point = max_length
# Geriye doğru git ve uygun kesme noktası ara
for i in range(max_length, max_length - 200, -1):
if i < len(remaining) and remaining[i] in ['.', '!', '?', '\n']:
cut_point = i + 1
break
elif i < len(remaining) and remaining[i] in [' ', ',', ';']:
cut_point = i
parts.append(remaining[:cut_point].strip())
remaining = remaining[cut_point:].strip()
if remaining:
parts.append(remaining)
return parts
# ===============================
# HAFIZA SİSTEMİ SONU
# ===============================
# WhatsApp mesajı işleme (eski fonksiyon - yedek için)
def process_whatsapp_message(user_message):
try:
system_messages = get_system_messages()
# 🔍 BF Space: Use improved product search if available (backup function)
product_found_improved = False
if USE_IMPROVED_SEARCH and improved_whatsapp_bot:
try:
product_result = improved_whatsapp_bot.process_message(user_message)
if product_result['is_product_query'] and product_result['response']:
# Check if user is asking about specific warehouse/store location
if any(keyword in user_message.lower() for keyword in ['mağaza', 'mağazada', 'nerede', 'hangi mağaza', 'şube']):
# Get warehouse stock info for the found products
if product_result['products_found']:
warehouse_info_parts = []
for product in product_result['products_found'][:2]: # Max 2 products
product_name = product[2] # Full product name
warehouse_stock = get_warehouse_stock(product_name)
if warehouse_stock:
warehouse_info_parts.append(f"{product_name} mağaza stogu:")
warehouse_info_parts.extend(warehouse_stock)
warehouse_info_parts.append("")
if warehouse_info_parts:
warehouse_response = "\n".join(warehouse_info_parts)
system_messages.append({
"role": "system",
"content": f"MAĞAZA STOK BİLGİSİ (BF Space Backup):\n{warehouse_response}\n\nSADECE bu bilgileri kullanarak kullanıcıya yardımcı ol."
})
product_found_improved = True
if not product_found_improved:
system_messages.append({
"role": "system",
"content": f"ÜRÜN BİLGİSİ (BF Space Backup):\n{product_result['response']}\n\nSADECE bu bilgileri kullanarak kullanıcıya yardımcı ol. Bu bilgiler dışında ek bilgi ekleme."
})
product_found_improved = True
except Exception as e:
print(f"BF Space backup: Improved search error: {e}")
# Fallback to basic search
if not product_found_improved:
# Ürün bilgilerini kontrol et (basic search)
input_words = user_message.lower().split()
for product_info in products:
if product_info[0] in input_words:
if product_info[1][0] == "stokta":
normal_price = f"Fiyat: {product_info[1][1]} TL"
if product_info[1][3]:
eft_price = f"Havale: {product_info[1][3]} TL"
price_info = f"{normal_price}, {eft_price}"
else:
price_info = normal_price
new_msg = f"{product_info[2]} {product_info[1][0]} - {price_info}"
else:
new_msg = f"{product_info[2]} {product_info[1][0]}"
system_messages.append({"role": "system", "content": new_msg})
break
messages = system_messages + [{"role": "user", "content": user_message}]
if not OPENAI_API_KEY:
return "OpenAI API anahtarı eksik. Lütfen environment variables'ları kontrol edin."
payload = {
"model": "gpt-5-chat-latest",
"messages": messages,
"temperature": 0,
"max_tokens": 800,
"stream": False,
}
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {OPENAI_API_KEY}"
}
response = requests.post(API_URL, headers=headers, json=payload)
if response.status_code == 200:
result = response.json()
return result['choices'][0]['message']['content']
else:
print(f"OpenAI API Error: {response.status_code} - {response.text}")
return f"API hatası: {response.status_code}. Lütfen daha sonra tekrar deneyin."
except Exception as e:
print(f"❌ WhatsApp mesaj işleme hatası: {e}")
return "Teknik bir sorun oluştu. Lütfen daha sonra tekrar deneyin."
# FastAPI uygulaması
app = FastAPI()
@app.post("/whatsapp-webhook")
async def whatsapp_webhook(request: Request):
try:
form_data = await request.form()
from_number = form_data.get('From')
to_number = form_data.get('To')
message_body = form_data.get('Body')
message_status = form_data.get('MessageStatus')
print(f"📱 Webhook - From: {from_number}, Body: {message_body}, Status: {message_status}")
# Durum güncellemelerini ignore et
if message_status in ['sent', 'delivered', 'read', 'failed']:
return {"status": "ignored", "message": f"Status: {message_status}"}
# Giden mesajları ignore et
if to_number != TWILIO_WHATSAPP_NUMBER:
return {"status": "ignored", "message": "Outgoing message"}
# Boş mesaj kontrolü
if not message_body or message_body.strip() == "":
return {"status": "ignored", "message": "Empty message"}
print(f"✅ MESAJ ALINDI: {from_number} -> {message_body}")
if not twilio_client:
return {"status": "error", "message": "Twilio yapılandırması eksik"}
# HAFIZALİ MESAJ İŞLEME
ai_response = process_whatsapp_message_with_memory(message_body, from_number)
# Mesajı parçalara böl
message_parts = split_long_message(ai_response, max_length=1600)
sent_messages = []
# Her parçayı sırayla gönder
for i, part in enumerate(message_parts):
if len(message_parts) > 1:
if i == 0:
part = f"{part}\n\n(1/{len(message_parts)})"
elif i == len(message_parts) - 1:
part = f"({i+1}/{len(message_parts)})\n\n{part}"
else:
part = f"({i+1}/{len(message_parts)})\n\n{part}"
# WhatsApp'a gönder
message = twilio_client.messages.create(
messaging_service_sid=TWILIO_MESSAGING_SERVICE_SID,
body=part,
to=from_number
)
sent_messages.append(message.sid)
if i < len(message_parts) - 1:
import time
time.sleep(0.5)
print(f"✅ {len(message_parts)} PARÇA GÖNDERİLDİ")
# Debug için mevcut kategoriyi logla
context = get_conversation_context(from_number)
if context.get("current_category"):
print(f"💭 Aktif kategori: {context['current_category']}")
return {
"status": "success",
"message_parts": len(message_parts),
"message_sids": sent_messages,
"current_category": context.get("current_category")
}
except Exception as e:
print(f"❌ Webhook hatası: {str(e)}")
return {"status": "error", "message": str(e)}
@app.get("/")
async def root():
return {"message": "Trek WhatsApp Bot çalışıyor!", "status": "active"}
# Hafızayı temizleme endpoint'i
@app.get("/clear-memory/{phone_number}")
async def clear_memory(phone_number: str):
"""Belirli bir telefon numarasının hafızasını temizle"""
if phone_number in conversation_memory:
del conversation_memory[phone_number]
return {"status": "success", "message": f"{phone_number} hafızası temizlendi"}
return {"status": "info", "message": "Hafıza bulunamadı"}
# Tüm hafızayı görme endpoint'i
@app.get("/debug-memory")
async def debug_memory():
"""Tüm hafızayı görüntüle (debug için)"""
memory_info = {}
for phone, context in conversation_memory.items():
memory_info[phone] = {
"current_category": context.get("current_category"),
"message_count": len(context.get("messages", [])),
"last_activity": str(context.get("last_activity"))
}
return {"conversation_memory": memory_info}
# Profil bilgilerini görme endpoint'i
@app.get("/debug-profile/{phone_number}")
async def debug_profile(phone_number: str):
"""Belirli kullanıcının profil bilgilerini görüntüle"""
profile_summary = get_user_profile_summary(phone_number)
return {"phone_number": phone_number, "profile": profile_summary}
# Tüm profilleri görme endpoint'i
@app.get("/debug-profiles")
async def debug_profiles():
"""Tüm kullanıcı profillerini görüntüle"""
from whatsapp_passive_profiler import passive_profiler
all_profiles = {}
for phone_number in passive_profiler.profiles.keys():
all_profiles[phone_number] = get_user_profile_summary(phone_number)
return {"profiles": all_profiles}
@app.get("/health")
async def health():
return {
"status": "healthy",
"twilio_configured": twilio_client is not None,
"openai_configured": OPENAI_API_KEY is not None,
"products_loaded": len(products),
"webhook_endpoint": "/whatsapp-webhook"
}
if __name__ == "__main__":
import uvicorn
print("🚀 Trek WhatsApp Bot başlatılıyor...")
uvicorn.run(app, host="0.0.0.0", port=7860)