SamiKoen commited on
Commit
33514db
·
1 Parent(s): 593ec02

Push advanced version with all features

Browse files

- Follow-up system for customer tracking
- Enhanced notification system with 60+ keywords
- Improved stock messages with clear out-of-stock indicators
- Notification viewer endpoint
- All features that were working before Twilio issue

The issue was in Twilio webhook configuration, not the code

__pycache__/follow_up_system.cpython-312.pyc ADDED
Binary file (15.2 kB). View file
 
__pycache__/smart_warehouse_with_price.cpython-312.pyc CHANGED
Binary files a/__pycache__/smart_warehouse_with_price.cpython-312.pyc and b/__pycache__/smart_warehouse_with_price.cpython-312.pyc differ
 
__pycache__/store_notification.cpython-312.pyc CHANGED
Binary files a/__pycache__/store_notification.cpython-312.pyc and b/__pycache__/store_notification.cpython-312.pyc differ
 
app.py CHANGED
@@ -68,6 +68,21 @@ except ImportError:
68
  USE_STORE_NOTIFICATION = False
69
  logger.info("❌ Store Notification System not available")
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  # Import Intent Analyzer
72
  try:
73
  from intent_analyzer import (
@@ -89,8 +104,8 @@ logger.info(f"OpenAI API Key var mı: {'Evet' if OPENAI_API_KEY else 'Hayır'}")
89
  # Twilio WhatsApp ayarları
90
  TWILIO_ACCOUNT_SID = os.getenv("TWILIO_ACCOUNT_SID")
91
  TWILIO_AUTH_TOKEN = os.getenv("TWILIO_AUTH_TOKEN")
92
- TWILIO_MESSAGING_SERVICE_SID = os.getenv("TWILIO_MESSAGING_SERVICE_SID")
93
- TWILIO_WHATSAPP_NUMBER = "whatsapp:+905332047254"
94
 
95
  logger.info(f"Twilio SID var mı: {'Evet' if TWILIO_ACCOUNT_SID else 'Hayır'}")
96
  logger.info(f"Twilio Auth Token var mı: {'Evet' if TWILIO_AUTH_TOKEN else 'Hayır'}")
@@ -982,6 +997,22 @@ def process_whatsapp_message_with_memory(user_message, phone_number):
982
  logger.info(f" 📍 Sebep: {notification_reason}")
983
  logger.info(f" ⚡ Öncelik: {urgency}")
984
  logger.info(f" 📦 Ürün: {product}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
985
  else:
986
  logger.error("❌ Mehmet Bey'e bildirim gönderilemedi")
987
 
@@ -1541,6 +1572,129 @@ async def clear_memory(phone_number: str):
1541
  return {"status": "success", "message": f"{phone_number} hafızası temizlendi"}
1542
  return {"status": "info", "message": "Hafıza bulunamadı"}
1543
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1544
  # Tüm hafızayı görme endpoint'i
1545
  @app.get("/debug-memory")
1546
  async def debug_memory():
 
68
  USE_STORE_NOTIFICATION = False
69
  logger.info("❌ Store Notification System not available")
70
 
71
+ # Import Follow-Up System
72
+ try:
73
+ from follow_up_system import (
74
+ FollowUpManager,
75
+ analyze_message_for_follow_up,
76
+ FollowUpType
77
+ )
78
+ USE_FOLLOW_UP = True
79
+ follow_up_manager = FollowUpManager()
80
+ logger.info("✅ Follow-Up System loaded")
81
+ except ImportError:
82
+ USE_FOLLOW_UP = False
83
+ follow_up_manager = None
84
+ logger.info("❌ Follow-Up System not available")
85
+
86
  # Import Intent Analyzer
87
  try:
88
  from intent_analyzer import (
 
104
  # Twilio WhatsApp ayarları
105
  TWILIO_ACCOUNT_SID = os.getenv("TWILIO_ACCOUNT_SID")
106
  TWILIO_AUTH_TOKEN = os.getenv("TWILIO_AUTH_TOKEN")
107
+ TWILIO_MESSAGING_SERVICE_SID = os.getenv("TWILIO_MESSAGING_SERVICE_SID", "MG11c1dfac28ad5f81908ec9ede0f7247f")
108
+ TWILIO_WHATSAPP_NUMBER = "whatsapp:+905332047254" # Bizim WhatsApp Business numaramız
109
 
110
  logger.info(f"Twilio SID var mı: {'Evet' if TWILIO_ACCOUNT_SID else 'Hayır'}")
111
  logger.info(f"Twilio Auth Token var mı: {'Evet' if TWILIO_AUTH_TOKEN else 'Hayır'}")
 
997
  logger.info(f" 📍 Sebep: {notification_reason}")
998
  logger.info(f" ⚡ Öncelik: {urgency}")
999
  logger.info(f" 📦 Ürün: {product}")
1000
+
1001
+ # TAKIP SISTEMINI KONTROL ET
1002
+ if USE_FOLLOW_UP and follow_up_manager:
1003
+ follow_up_analysis = analyze_message_for_follow_up(user_message)
1004
+ if follow_up_analysis and follow_up_analysis["needs_follow_up"]:
1005
+ # Takip oluştur
1006
+ follow_up = follow_up_manager.create_follow_up(
1007
+ customer_phone=phone_number,
1008
+ product_name=product,
1009
+ follow_up_type=follow_up_analysis["follow_up_type"],
1010
+ original_message=user_message,
1011
+ follow_up_hours=follow_up_analysis["follow_up_hours"],
1012
+ notes=follow_up_analysis["reason"]
1013
+ )
1014
+ logger.info(f"📌 Takip oluşturuldu: {follow_up_analysis['reason']}")
1015
+ logger.info(f" ⏰ {follow_up_analysis['follow_up_hours']} saat sonra hatırlatılacak")
1016
  else:
1017
  logger.error("❌ Mehmet Bey'e bildirim gönderilemedi")
1018
 
 
1572
  return {"status": "success", "message": f"{phone_number} hafızası temizlendi"}
1573
  return {"status": "info", "message": "Hafıza bulunamadı"}
1574
 
1575
+ # Mehmet Bey bildirimlerini görüntüleme endpoint'i
1576
+ @app.get("/mehmet-bey-notifications")
1577
+ async def view_mehmet_bey_notifications():
1578
+ """Mehmet Bey için kaydedilen bildirimleri görüntüle"""
1579
+ import json
1580
+ from datetime import datetime, timedelta
1581
+
1582
+ try:
1583
+ with open("mehmet_bey_notifications.json", "r") as f:
1584
+ notifications = json.load(f)
1585
+
1586
+ # Son 24 saatin bildirimlerini filtrele
1587
+ now = datetime.now()
1588
+ recent_notifications = []
1589
+
1590
+ for notif in notifications:
1591
+ notif_time = datetime.fromisoformat(notif["timestamp"])
1592
+ if now - notif_time < timedelta(hours=24):
1593
+ recent_notifications.append(notif)
1594
+
1595
+ # HTML formatında döndür
1596
+ html_content = """
1597
+ <html>
1598
+ <head>
1599
+ <title>Mehmet Bey Bildirimleri</title>
1600
+ <style>
1601
+ body { font-family: Arial; margin: 20px; background: #f5f5f5; }
1602
+ .notification {
1603
+ background: white;
1604
+ border: 1px solid #ddd;
1605
+ padding: 15px;
1606
+ margin: 10px 0;
1607
+ border-radius: 8px;
1608
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
1609
+ }
1610
+ .urgent { border-left: 5px solid #ff4444; }
1611
+ .medium { border-left: 5px solid #ffaa00; }
1612
+ .timestamp { color: #666; font-size: 0.9em; }
1613
+ .action {
1614
+ display: inline-block;
1615
+ padding: 3px 8px;
1616
+ border-radius: 4px;
1617
+ font-size: 0.85em;
1618
+ font-weight: bold;
1619
+ }
1620
+ .reserve { background: #ffebee; color: #c62828; }
1621
+ .price { background: #fff3e0; color: #f57c00; }
1622
+ .stock { background: #e3f2fd; color: #1976d2; }
1623
+ .test { background: #f3e5f5; color: #7b1fa2; }
1624
+ h1 { color: #333; }
1625
+ .stats {
1626
+ background: #2196F3;
1627
+ color: white;
1628
+ padding: 10px 20px;
1629
+ border-radius: 5px;
1630
+ display: inline-block;
1631
+ margin-bottom: 20px;
1632
+ }
1633
+ pre {
1634
+ background: #f9f9f9;
1635
+ padding: 10px;
1636
+ border-radius: 4px;
1637
+ white-space: pre-wrap;
1638
+ font-size: 0.9em;
1639
+ }
1640
+ </style>
1641
+ <meta charset="utf-8">
1642
+ </head>
1643
+ <body>
1644
+ <h1>🔔 Mehmet Bey Bildirim Sistemi</h1>
1645
+ <div class="stats">
1646
+ 📊 Toplam: """ + str(len(notifications)) + """ bildirim |
1647
+ 🕐 Son 24 saat: """ + str(len(recent_notifications)) + """ bildirim
1648
+ </div>
1649
+ """
1650
+
1651
+ # Son 20 bildirimi göster (en yeniden eskiye)
1652
+ for notif in reversed(notifications[-20:]):
1653
+ timestamp = notif.get('timestamp', '')
1654
+ action = notif.get('action', 'info')
1655
+ product = notif.get('product_name', 'Belirtilmemiş')
1656
+ customer = notif.get('customer_phone', '').replace('whatsapp:', '')
1657
+ message = notif.get('formatted_message', '')
1658
+
1659
+ # CSS class belirleme
1660
+ urgency_class = ''
1661
+ if 'reserve' in action or 'ayırt' in message.lower():
1662
+ urgency_class = 'urgent'
1663
+ elif 'price' in action or 'stock' in action:
1664
+ urgency_class = 'medium'
1665
+
1666
+ action_class = action
1667
+
1668
+ html_content += f'''
1669
+ <div class="notification {urgency_class}">
1670
+ <div class="timestamp">⏰ {timestamp}</div>
1671
+ <div style="margin-top: 10px;">
1672
+ <span class="action {action_class}">{action.upper()}</span>
1673
+ <strong>{product}</strong>
1674
+ </div>
1675
+ <div style="margin-top: 5px;">📱 Müşteri: {customer}</div>
1676
+ <pre>{message}</pre>
1677
+ </div>
1678
+ '''
1679
+
1680
+ html_content += """
1681
+ </body>
1682
+ </html>
1683
+ """
1684
+
1685
+ from fastapi.responses import HTMLResponse
1686
+ return HTMLResponse(content=html_content)
1687
+
1688
+ except FileNotFoundError:
1689
+ return HTMLResponse(content="""
1690
+ <html><body style="font-family: Arial; padding: 40px; text-align: center;">
1691
+ <h2>📭 Henüz bildirim yok</h2>
1692
+ <p>Müşteri mesajları geldiğinde burada görünecek.</p>
1693
+ </body></html>
1694
+ """)
1695
+ except Exception as e:
1696
+ return {"status": "error", "message": str(e)}
1697
+
1698
  # Tüm hafızayı görme endpoint'i
1699
  @app.get("/debug-memory")
1700
  async def debug_memory():
app_backup_20250903_1710.py ADDED
@@ -0,0 +1,1668 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import requests
4
+ import xml.etree.ElementTree as ET
5
+ import warnings
6
+ import time
7
+ import threading
8
+ from concurrent.futures import ThreadPoolExecutor, as_completed
9
+ from fastapi import FastAPI, Request
10
+ from twilio.rest import Client
11
+ from twilio.twiml.messaging_response import MessagingResponse
12
+
13
+ # Yeni modüller - Basit sistem
14
+ from prompts import get_prompt_content_only
15
+ from whatsapp_renderer import extract_product_info_whatsapp
16
+ from whatsapp_passive_profiler import (
17
+ analyze_user_message, get_user_profile_summary, get_personalized_recommendations
18
+ )
19
+
20
+ # LOGGING EN BAŞA EKLENDİ
21
+ import logging
22
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
23
+ logger = logging.getLogger(__name__)
24
+
25
+ # Import improved WhatsApp search for BF space
26
+ # DISABLED - Using GPT-5 smart warehouse search instead
27
+ USE_IMPROVED_SEARCH = False
28
+ # try:
29
+ # from whatsapp_improved_chatbot import WhatsAppImprovedChatbot
30
+ # USE_IMPROVED_SEARCH = True
31
+ # except ImportError:
32
+ # print("Improved WhatsApp chatbot not available, using basic search")
33
+ # USE_IMPROVED_SEARCH = False
34
+
35
+ # Import GPT-5 powered smart warehouse search - complete BF algorithm
36
+ try:
37
+ from smart_warehouse_with_price import get_warehouse_stock_smart_with_price
38
+ USE_GPT5_SEARCH = True
39
+ logger.info("✅ GPT-5 complete smart warehouse with price (BF algorithm) loaded")
40
+ except ImportError:
41
+ USE_GPT5_SEARCH = False
42
+ logger.info("❌ GPT-5 search not available")
43
+
44
+ warnings.simplefilter('ignore')
45
+
46
+ # Import Media Queue V2
47
+ try:
48
+ from media_queue_v2 import media_queue
49
+ USE_MEDIA_QUEUE = True
50
+ logger.info("✅ Media Queue V2 loaded successfully")
51
+ except ImportError:
52
+ USE_MEDIA_QUEUE = False
53
+ logger.info("❌ Media Queue V2 not available")
54
+
55
+ # Import Store Notification System
56
+ try:
57
+ from store_notification import (
58
+ notify_product_reservation,
59
+ notify_price_inquiry,
60
+ notify_stock_inquiry,
61
+ send_test_notification,
62
+ send_store_notification,
63
+ should_notify_mehmet_bey
64
+ )
65
+ USE_STORE_NOTIFICATION = True
66
+ logger.info("✅ Store Notification System loaded")
67
+ except ImportError:
68
+ USE_STORE_NOTIFICATION = False
69
+ logger.info("❌ Store Notification System not available")
70
+
71
+ # Import Intent Analyzer
72
+ try:
73
+ from intent_analyzer import (
74
+ analyze_customer_intent,
75
+ should_notify_store,
76
+ get_smart_notification_message
77
+ )
78
+ USE_INTENT_ANALYZER = True
79
+ logger.info("✅ GPT-5 Intent Analyzer loaded")
80
+ except ImportError:
81
+ USE_INTENT_ANALYZER = False
82
+ logger.info("❌ Intent Analyzer not available")
83
+
84
+ # API ayarları
85
+ API_URL = "https://api.openai.com/v1/chat/completions"
86
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
87
+ logger.info(f"OpenAI API Key var mı: {'Evet' if OPENAI_API_KEY else 'Hayır'}")
88
+
89
+ # Twilio WhatsApp ayarları
90
+ TWILIO_ACCOUNT_SID = os.getenv("TWILIO_ACCOUNT_SID")
91
+ TWILIO_AUTH_TOKEN = os.getenv("TWILIO_AUTH_TOKEN")
92
+ TWILIO_MESSAGING_SERVICE_SID = os.getenv("TWILIO_MESSAGING_SERVICE_SID")
93
+ TWILIO_WHATSAPP_NUMBER = "whatsapp:+905332047254"
94
+
95
+ logger.info(f"Twilio SID var mı: {'Evet' if TWILIO_ACCOUNT_SID else 'Hayır'}")
96
+ logger.info(f"Twilio Auth Token var mı: {'Evet' if TWILIO_AUTH_TOKEN else 'Hayır'}")
97
+ logger.info(f"Messaging Service SID var mı: {'Evet' if TWILIO_MESSAGING_SERVICE_SID else 'Hayır'}")
98
+
99
+ if not TWILIO_ACCOUNT_SID or not TWILIO_AUTH_TOKEN:
100
+ logger.error("❌ Twilio bilgileri eksik!")
101
+ twilio_client = None
102
+ else:
103
+ try:
104
+ twilio_client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
105
+ logger.info("✅ Twilio client başarıyla oluşturuldu!")
106
+ except Exception as e:
107
+ logger.error(f"❌ Twilio client hatası: {e}")
108
+ twilio_client = None
109
+
110
+ # Mağaza stok bilgilerini çekme fonksiyonu
111
+ def get_warehouse_stock(product_name):
112
+ """B2B API'den mağaza stok bilgilerini çek - GPT-5 enhanced"""
113
+ # Try GPT-5 complete smart search (BF algorithm)
114
+ if USE_GPT5_SEARCH:
115
+ try:
116
+ gpt5_result = get_warehouse_stock_smart_with_price(product_name)
117
+ if gpt5_result and isinstance(gpt5_result, list):
118
+ # Return directly if it's already formatted strings
119
+ if all(isinstance(item, str) for item in gpt5_result):
120
+ return gpt5_result
121
+ # Format for WhatsApp if dict format
122
+ warehouse_info = []
123
+ for item in gpt5_result:
124
+ if isinstance(item, dict):
125
+ info = f"📦 {item.get('name', '')}"
126
+ if item.get('variant'):
127
+ info += f" ({item['variant']})"
128
+ if item.get('warehouses'):
129
+ info += f"\n📍 Mevcut: {', '.join(item['warehouses'])}"
130
+ if item.get('price'):
131
+ info += f"\n💰 {item['price']}"
132
+ warehouse_info.append(info)
133
+ else:
134
+ warehouse_info.append(str(item))
135
+ return warehouse_info if warehouse_info else None
136
+ except Exception as e:
137
+ logger.error(f"GPT-5 warehouse search error: {e}")
138
+ # Continue to fallback search
139
+
140
+ # Fallback to original search
141
+ try:
142
+ import re
143
+ warehouse_url = 'https://video.trek-turkey.com/bizimhesap-warehouse-xml-b2b-api-v2.php'
144
+ response = requests.get(warehouse_url, verify=False, timeout=15)
145
+
146
+ if response.status_code != 200:
147
+ return None
148
+
149
+ root = ET.fromstring(response.content)
150
+
151
+ # Turkish character normalization function
152
+ turkish_map = {'ı': 'i', 'ğ': 'g', 'ü': 'u', 'ş': 's', 'ö': 'o', 'ç': 'c', 'İ': 'i', 'I': 'i'}
153
+
154
+ def normalize_turkish(text):
155
+ import unicodedata
156
+ text = unicodedata.normalize('NFD', text)
157
+ text = ''.join(char for char in text if unicodedata.category(char) != 'Mn')
158
+ for tr_char, en_char in turkish_map.items():
159
+ text = text.replace(tr_char, en_char)
160
+ return text
161
+
162
+ # Normalize search product name
163
+ search_name = normalize_turkish(product_name.lower().strip())
164
+ search_name = search_name.replace('(2026)', '').replace('(2025)', '').replace(' gen 3', '').replace(' gen', '').strip()
165
+ search_words = search_name.split()
166
+
167
+ best_matches = []
168
+ exact_matches = []
169
+ variant_matches = []
170
+ candidates = []
171
+
172
+ # Separate size/color words from product words
173
+ size_color_words = ['s', 'm', 'l', 'xl', 'xs', 'small', 'medium', 'large',
174
+ 'turuncu', 'siyah', 'beyaz', 'mavi', 'kirmizi', 'yesil',
175
+ 'orange', 'black', 'white', 'blue', 'red', 'green']
176
+
177
+ variant_words = [word for word in search_words if word in size_color_words]
178
+ product_words = [word for word in search_words if word not in size_color_words]
179
+
180
+ # Check if this is a size/color specific query
181
+ is_size_color_query = len(variant_words) > 0 and len(search_words) <= 4
182
+
183
+ # İlk geçiş: Variant alanında beden/renk araması
184
+ if is_size_color_query:
185
+ for product in root.findall('Product'):
186
+ product_name_elem = product.find('ProductName')
187
+ variant_elem = product.find('ProductVariant')
188
+
189
+ if product_name_elem is not None and product_name_elem.text:
190
+ xml_product_name = product_name_elem.text.strip()
191
+ normalized_product_name = normalize_turkish(xml_product_name.lower())
192
+
193
+ # If there are product words, check if they match the product name
194
+ product_name_matches = True
195
+ if product_words:
196
+ product_name_matches = all(word in normalized_product_name for word in product_words)
197
+
198
+ # Only proceed if product name matches (or no product context)
199
+ if product_name_matches:
200
+ # Variant field check
201
+ if variant_elem is not None and variant_elem.text:
202
+ variant_text = normalize_turkish(variant_elem.text.lower().replace('-', ' '))
203
+
204
+ # Check if all variant words are in variant field
205
+ if all(word in variant_text for word in variant_words):
206
+ variant_matches.append((product, xml_product_name, variant_text))
207
+
208
+ if variant_matches:
209
+ candidates = variant_matches
210
+ else:
211
+ # Fallback to normal product name search
212
+ is_size_color_query = False
213
+
214
+ # İkinci geçiş: Normal ürün adı tam eşleşmeleri (variant match yoksa)
215
+ if not is_size_color_query or not candidates:
216
+ for product in root.findall('Product'):
217
+ product_name_elem = product.find('ProductName')
218
+ if product_name_elem is not None and product_name_elem.text:
219
+ xml_product_name = product_name_elem.text.strip()
220
+ normalized_xml = normalize_turkish(xml_product_name.lower())
221
+ normalized_xml = normalized_xml.replace('(2026)', '').replace('(2025)', '').replace(' gen 3', '').replace(' gen', '').strip()
222
+ xml_words = normalized_xml.split()
223
+
224
+ # Tam eşleşme kontrolü - ilk iki kelime tam aynı olmalı
225
+ if len(search_words) >= 2 and len(xml_words) >= 2:
226
+ search_key = f"{search_words[0]} {search_words[1]}"
227
+ xml_key = f"{xml_words[0]} {xml_words[1]}"
228
+
229
+ if search_key == xml_key:
230
+ exact_matches.append((product, xml_product_name, normalized_xml))
231
+
232
+ # Eğer variant match varsa onu kullan, yoksa exact matches kullan
233
+ if not candidates:
234
+ candidates = exact_matches if exact_matches else []
235
+
236
+ # Eğer hala bir match yoksa, fuzzy matching yap
237
+ if not candidates:
238
+ for product in root.findall('Product'):
239
+ product_name_elem = product.find('ProductName')
240
+ if product_name_elem is not None and product_name_elem.text:
241
+ xml_product_name = product_name_elem.text.strip()
242
+ normalized_xml = normalize_turkish(xml_product_name.lower())
243
+ normalized_xml = normalized_xml.replace('(2026)', '').replace('(2025)', '').replace(' gen 3', '').replace(' gen', '').strip()
244
+ xml_words = normalized_xml.split()
245
+
246
+ # Ortak kelime sayısını hesapla
247
+ common_words = set(search_words) & set(xml_words)
248
+
249
+ # En az 2 ortak kelime olmalı VE ilk kelime aynı olmalı (marka kontrolü)
250
+ if (len(common_words) >= 2 and
251
+ len(search_words) > 0 and len(xml_words) > 0 and
252
+ search_words[0] == xml_words[0]):
253
+ best_matches.append((product, xml_product_name, normalized_xml, len(common_words)))
254
+
255
+ # En çok ortak kelimeye sahip olanları seç
256
+ if best_matches:
257
+ max_common = max(match[3] for match in best_matches)
258
+ candidates = [(match[0], match[1], match[2]) for match in best_matches if match[3] == max_common]
259
+
260
+ # Stok bilgilerini topla ve tekrarları önle
261
+ warehouse_stock_map = {} # warehouse_name -> total_stock
262
+
263
+ for product, xml_name, _ in candidates:
264
+ # New B2B API structure: Warehouse elements are direct children of Product
265
+ for warehouse in product.findall('Warehouse'):
266
+ name_elem = warehouse.find('Name')
267
+ stock_elem = warehouse.find('Stock')
268
+
269
+ if name_elem is not None and stock_elem is not None:
270
+ warehouse_name = name_elem.text if name_elem.text else "Bilinmeyen"
271
+ try:
272
+ stock_count = int(stock_elem.text) if stock_elem.text else 0
273
+ if stock_count > 0:
274
+ # Aynı mağaza için stokları topla
275
+ if warehouse_name in warehouse_stock_map:
276
+ warehouse_stock_map[warehouse_name] += stock_count
277
+ else:
278
+ warehouse_stock_map[warehouse_name] = stock_count
279
+ except (ValueError, TypeError):
280
+ pass
281
+
282
+ if warehouse_stock_map:
283
+ # Mağaza stoklarını liste halinde döndür
284
+ all_warehouse_info = []
285
+ for warehouse_name, total_stock in warehouse_stock_map.items():
286
+ all_warehouse_info.append(f"{warehouse_name}: Stokta var")
287
+ return all_warehouse_info
288
+ else:
289
+ return ["Hiçbir mağazada stokta bulunmuyor"]
290
+
291
+ except Exception as e:
292
+ print(f"Mağaza stok bilgisi çekme hatası: {e}")
293
+ return None
294
+
295
+ # Trek bisiklet ürünlerini çekme - DÜZELTİLMİŞ VERSİYON
296
+ try:
297
+ # All XML debug prints disabled to reduce noise
298
+ url = 'https://www.trekbisiklet.com.tr/output/8582384479'
299
+ response = requests.get(url, verify=False, timeout=10)
300
+
301
+ # XML parsing - all debug prints disabled
302
+ content_preview = response.content[:500].decode('utf-8', errors='ignore')
303
+ root = ET.fromstring(response.content)
304
+ all_items = root.findall('item')
305
+
306
+ # Item analysis disabled for production
307
+
308
+ # Marlin arama testi
309
+ marlin_count = 0
310
+ products = []
311
+
312
+ for item in all_items:
313
+ # Değişkenleri önceden tanımla
314
+ stock_number = 0
315
+ stock_amount = "stokta değil"
316
+ price = ""
317
+ price_eft = ""
318
+ product_link = ""
319
+
320
+ rootlabel = item.find('rootlabel')
321
+ if rootlabel is None or not rootlabel.text:
322
+ continue
323
+
324
+ full_name = rootlabel.text.strip()
325
+ name_words = full_name.lower().split()
326
+ name = name_words[0] if name_words else "unknown"
327
+
328
+ # STOK KONTROLÜ - SAYISAL KARŞILAŞTIRMA
329
+ stock_element = item.find('stockAmount')
330
+ if stock_element is not None and stock_element.text:
331
+ try:
332
+ stock_number = int(stock_element.text.strip())
333
+ stock_amount = "stokta" if stock_number > 0 else "stokta değil"
334
+ except (ValueError, TypeError):
335
+ stock_number = 0
336
+ stock_amount = "stokta değil"
337
+
338
+ # Marlin kontrolü
339
+ if 'marlin' in full_name.lower():
340
+ marlin_count += 1
341
+ pass
342
+
343
+ # Stokta olan ürünler için fiyat bilgilerini al
344
+ if stock_amount == "stokta":
345
+ # Normal fiyat
346
+ price_element = item.find('priceTaxWithCur')
347
+ price_str = price_element.text if price_element is not None and price_element.text else "0"
348
+
349
+ # Kampanya fiyatı
350
+ price_rebate_element = item.find('priceRebateWithTax')
351
+ price_rebate_str = price_rebate_element.text if price_rebate_element is not None and price_rebate_element.text else ""
352
+
353
+ # Kampanya fiyatı varsa onu kullan, yoksa normal fiyatı kullan
354
+ final_price_str = price_str
355
+ if price_rebate_str:
356
+ try:
357
+ normal_price = float(price_str)
358
+ rebate_price = float(price_rebate_str)
359
+ # Kampanya fiyatı normal fiyattan farklı ve düşükse kullan
360
+ if rebate_price < normal_price:
361
+ final_price_str = price_rebate_str
362
+ except (ValueError, TypeError):
363
+ final_price_str = price_str
364
+
365
+ # EFT fiyatı
366
+ price_eft_element = item.find('priceEft')
367
+ price_eft_str = price_eft_element.text if price_eft_element is not None and price_eft_element.text else ""
368
+
369
+ # Ürün linki
370
+ link_element = item.find('productLink')
371
+ product_link = link_element.text if link_element is not None and link_element.text else ""
372
+
373
+ # Fiyat formatting (kampanya fiyatı veya normal fiyat)
374
+ try:
375
+ price_float = float(final_price_str)
376
+ if price_float > 200000:
377
+ price = str(round(price_float / 5000) * 5000)
378
+ elif price_float > 30000:
379
+ price = str(round(price_float / 1000) * 1000)
380
+ elif price_float > 10000:
381
+ price = str(round(price_float / 100) * 100)
382
+ else:
383
+ price = str(round(price_float / 10) * 10)
384
+ except (ValueError, TypeError):
385
+ price = final_price_str
386
+
387
+ # EFT fiyat formatting
388
+ if price_eft_str:
389
+ try:
390
+ price_eft_float = float(price_eft_str)
391
+ if price_eft_float > 200000:
392
+ price_eft = str(round(price_eft_float / 5000) * 5000)
393
+ elif price_eft_float > 30000:
394
+ price_eft = str(round(price_eft_float / 1000) * 1000)
395
+ elif price_eft_float > 10000:
396
+ price_eft = str(round(price_eft_float / 100) * 100)
397
+ else:
398
+ price_eft = str(round(price_eft_float / 10) * 10)
399
+ except (ValueError, TypeError):
400
+ price_eft = price_eft_str
401
+ else:
402
+ try:
403
+ price_eft_float = float(price_str)
404
+ price_eft = str(round(price_eft_float * 0.975 / 10) * 10)
405
+ except:
406
+ price_eft = ""
407
+
408
+ # Ürün bilgilerini tuple olarak oluştur
409
+ item_info = (stock_amount, price, product_link, price_eft, str(stock_number))
410
+ products.append((name, item_info, full_name))
411
+
412
+ # Summary disabled for production
413
+
414
+ # Initialize improved WhatsApp chatbot for BF space
415
+ global improved_whatsapp_bot
416
+ improved_whatsapp_bot = None
417
+ if USE_IMPROVED_SEARCH:
418
+ try:
419
+ improved_whatsapp_bot = WhatsAppImprovedChatbot(products)
420
+ # print("✅ BF Space: Improved WhatsApp product search initialized successfully")
421
+ except Exception as e:
422
+ logger.error(f"BF Space: Failed to initialize improved WhatsApp search: {e}")
423
+ USE_IMPROVED_SEARCH = False
424
+ improved_whatsapp_bot = None
425
+
426
+ # Enhanced features kaldırıldı - GPT-4 doğal dil anlama kullanacak
427
+ # print("✅ Basit sistem aktif - GPT-4 doğal dil anlama")
428
+
429
+ # Marlin debug reports disabled for production
430
+ if marlin_count == 0:
431
+ pass # No Marlin products found
432
+ else:
433
+ # Marlin stok raporu
434
+ marlin_products = [p for p in products if 'marlin' in p[2].lower()]
435
+ marlin_in_stock = [p for p in marlin_products if p[1][0] == "stokta"]
436
+ marlin_out_of_stock = [p for p in marlin_products if p[1][0] == "stokta değil"]
437
+
438
+ # Product lists disabled for production
439
+ pass
440
+
441
+ except Exception as e:
442
+ logger.error(f"Ürün yükleme hatası: {e}")
443
+ import traceback
444
+ traceback.print_exc()
445
+ products = []
446
+
447
+ # ===============================
448
+ # STOK API ENTEGRASYONU
449
+ # ===============================
450
+
451
+ STOCK_API_BASE = "https://video.trek-turkey.com/bizimhesap-proxy.php"
452
+
453
+ # Stock cache (5 dakikalık cache)
454
+ stock_cache = {}
455
+ CACHE_DURATION = 300 # 5 dakika (saniye cinsinden)
456
+
457
+ def normalize_turkish(text):
458
+ """Türkçe karakterleri normalize et"""
459
+ if not text:
460
+ return ""
461
+ replacements = {
462
+ 'ı': 'i', 'İ': 'i', 'ş': 's', 'Ş': 's',
463
+ 'ğ': 'g', 'Ğ': 'g', 'ü': 'u', 'Ü': 'u',
464
+ 'ö': 'o', 'Ö': 'o', 'ç': 'c', 'Ç': 'c'
465
+ }
466
+ text = text.lower()
467
+ for tr_char, eng_char in replacements.items():
468
+ text = text.replace(tr_char, eng_char)
469
+ return text
470
+
471
+ def fetch_warehouse_inventory(warehouse, product_name, search_terms):
472
+ """Tek bir mağazanın stok bilgisini al"""
473
+ try:
474
+ warehouse_id = warehouse['id']
475
+ warehouse_name = warehouse['title']
476
+
477
+ # DSW'yi ayrı tut (gelecek stok için)
478
+ is_dsw = 'DSW' in warehouse_name or 'ÖN SİPARİŞ' in warehouse_name.upper()
479
+
480
+ # Mağaza stoklarını al
481
+ inventory_url = f"{STOCK_API_BASE}?action=inventory&warehouse={warehouse_id}&endpoint=inventory/{warehouse_id}"
482
+ inventory_response = requests.get(inventory_url, timeout=3, verify=False)
483
+
484
+ if inventory_response.status_code != 200:
485
+ return None
486
+
487
+ inventory_data = inventory_response.json()
488
+
489
+ # API yanıtını kontrol et
490
+ if 'data' not in inventory_data or 'inventory' not in inventory_data['data']:
491
+ return None
492
+
493
+ products = inventory_data['data']['inventory']
494
+
495
+ # Beden terimleri kontrolü
496
+ size_terms = ['xs', 's', 'm', 'ml', 'l', 'xl', 'xxl', '2xl', '3xl', 'small', 'medium', 'large']
497
+ size_numbers = ['44', '46', '48', '50', '52', '54', '56', '58', '60']
498
+
499
+ # Arama terimlerinde beden var mı kontrol et
500
+ has_size_query = False
501
+ size_query = None
502
+ for term in search_terms:
503
+ if term in size_terms or term in size_numbers:
504
+ has_size_query = True
505
+ size_query = term
506
+ break
507
+
508
+ # Eğer sadece beden sorgusu varsa (ör: "m", "xl")
509
+ is_only_size_query = len(search_terms) == 1 and has_size_query
510
+
511
+ # Ürünü ara
512
+ warehouse_variants = []
513
+ dsw_stock_count = 0
514
+
515
+ for product in products:
516
+ product_title = normalize_turkish(product.get('title', '')).lower()
517
+ original_title = product.get('title', '')
518
+
519
+ # Eğer sadece beden sorgusu ise
520
+ if is_only_size_query:
521
+ # Beden terimini ürün başlığında ara (parantez içinde veya dışında)
522
+ 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}'):
523
+ qty = int(product.get('qty', 0))
524
+ stock = int(product.get('stock', 0))
525
+ actual_stock = max(qty, stock)
526
+
527
+ if actual_stock > 0:
528
+ if is_dsw:
529
+ dsw_stock_count += actual_stock
530
+ continue
531
+ warehouse_variants.append(f"{original_title}: ✓ Stokta")
532
+ else:
533
+ # Normal ürün araması - tüm terimler eşleşmeli
534
+ # Ama beden terimi varsa özel kontrol yap
535
+ if has_size_query:
536
+ # Beden hariç diğer terimleri kontrol et
537
+ non_size_terms = [t for t in search_terms if t != size_query]
538
+ product_matches = all(term in product_title for term in non_size_terms)
539
+
540
+ # Beden kontrolü - daha esnek
541
+ 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}')
542
+
543
+ if product_matches and size_matches:
544
+ qty = int(product.get('qty', 0))
545
+ stock = int(product.get('stock', 0))
546
+ actual_stock = max(qty, stock)
547
+
548
+ if actual_stock > 0:
549
+ if is_dsw:
550
+ dsw_stock_count += actual_stock
551
+ continue
552
+
553
+ # Varyant bilgisini göster
554
+ variant_info = original_title
555
+ possible_names = [
556
+ product_name.upper(),
557
+ product_name.lower(),
558
+ product_name.title(),
559
+ product_name.upper().replace('I', 'İ'),
560
+ product_name.upper().replace('İ', 'I'),
561
+ ]
562
+
563
+ if 'fx sport' in product_name.lower():
564
+ possible_names.extend(['FX Sport AL 3', 'FX SPORT AL 3', 'Fx Sport Al 3'])
565
+
566
+ for possible_name in possible_names:
567
+ variant_info = variant_info.replace(possible_name, '').strip()
568
+
569
+ variant_info = ' '.join(variant_info.split())
570
+
571
+ if variant_info and variant_info != original_title:
572
+ warehouse_variants.append(f"{variant_info}: ✓ Stokta")
573
+ else:
574
+ warehouse_variants.append(f"{original_title}: ✓ Stokta")
575
+ else:
576
+ # Beden sorgusu yoksa normal kontrol
577
+ if all(term in product_title for term in search_terms):
578
+ qty = int(product.get('qty', 0))
579
+ stock = int(product.get('stock', 0))
580
+ actual_stock = max(qty, stock)
581
+
582
+ if actual_stock > 0:
583
+ if is_dsw:
584
+ dsw_stock_count += actual_stock
585
+ continue
586
+
587
+ variant_info = original_title
588
+ possible_names = [
589
+ product_name.upper(),
590
+ product_name.lower(),
591
+ product_name.title(),
592
+ product_name.upper().replace('I', 'İ'),
593
+ product_name.upper().replace('İ', 'I'),
594
+ ]
595
+
596
+ if 'fx sport' in product_name.lower():
597
+ possible_names.extend(['FX Sport AL 3', 'FX SPORT AL 3', 'Fx Sport Al 3'])
598
+
599
+ for possible_name in possible_names:
600
+ variant_info = variant_info.replace(possible_name, '').strip()
601
+
602
+ variant_info = ' '.join(variant_info.split())
603
+
604
+ if variant_info and variant_info != original_title:
605
+ warehouse_variants.append(f"{variant_info}: ✓ Stokta")
606
+ else:
607
+ warehouse_variants.append(f"{original_title}: ✓ Stokta")
608
+
609
+ # Sonuç döndür
610
+ if warehouse_variants and not is_dsw:
611
+ return {'warehouse': warehouse_name, 'variants': warehouse_variants, 'is_dsw': False}
612
+ elif dsw_stock_count > 0:
613
+ return {'dsw_stock': dsw_stock_count, 'is_dsw': True}
614
+
615
+ return None
616
+
617
+ except Exception:
618
+ return None
619
+
620
+ def get_realtime_stock_parallel(product_name):
621
+ """API'den gerçek zamanlı stok bilgisini çek - Paralel versiyon with cache"""
622
+ try:
623
+ # Cache kontrolü
624
+ cache_key = normalize_turkish(product_name).lower()
625
+ current_time = time.time()
626
+
627
+ if cache_key in stock_cache:
628
+ cached_data, cached_time = stock_cache[cache_key]
629
+ # Cache hala geçerli mi?
630
+ if current_time - cached_time < CACHE_DURATION:
631
+ logger.info(f"Cache'den döndürülüyor: {product_name}")
632
+ return cached_data
633
+
634
+ # Önce mağaza listesini al
635
+ warehouses_url = f"{STOCK_API_BASE}?action=warehouses&endpoint=warehouses"
636
+ warehouses_response = requests.get(warehouses_url, timeout=3, verify=False)
637
+
638
+ if warehouses_response.status_code != 200:
639
+ logger.error(f"Mağaza listesi alınamadı: {warehouses_response.status_code}")
640
+ return None
641
+
642
+ warehouses_data = warehouses_response.json()
643
+
644
+ # API yanıtını kontrol et
645
+ if 'data' not in warehouses_data or 'warehouses' not in warehouses_data['data']:
646
+ logger.error("Mağaza verisi bulunamadı")
647
+ return None
648
+
649
+ warehouses = warehouses_data['data']['warehouses']
650
+
651
+ # Ürün adını normalize et
652
+ search_terms = normalize_turkish(product_name).lower().split()
653
+ logger.info(f"Aranan ürün: {product_name} -> {search_terms}")
654
+
655
+ stock_info = {}
656
+ total_dsw_stock = 0
657
+ total_stock = 0
658
+
659
+ # Paralel olarak tüm mağazaları sorgula
660
+ with ThreadPoolExecutor(max_workers=10) as executor:
661
+ # Tüm mağazalar için görev oluştur
662
+ futures = {
663
+ executor.submit(fetch_warehouse_inventory, warehouse, product_name, search_terms): warehouse
664
+ for warehouse in warehouses
665
+ }
666
+
667
+ # Sonuçları topla
668
+ for future in as_completed(futures):
669
+ result = future.result()
670
+ if result:
671
+ if result.get('is_dsw'):
672
+ total_dsw_stock += result.get('dsw_stock', 0)
673
+ else:
674
+ warehouse_name = result['warehouse']
675
+ stock_info[warehouse_name] = result['variants']
676
+ total_stock += 1 # En az bir mağazada var
677
+
678
+ # Sonucu oluştur
679
+ if not stock_info:
680
+ # Mağazada yok ama DSW'de varsa
681
+ if total_dsw_stock > 0:
682
+ result = f"{product_name}: Şu anda mağazalarda stokta yok, ancak yakında gelecek. Ön sipariş verebilirsiniz."
683
+ else:
684
+ result = f"{product_name}: Şu anda hiçbir mağazada stokta bulunmuyor."
685
+ else:
686
+ # Minimal prompt oluştur - varyant detaylarıyla
687
+ prompt_lines = [f"{product_name} stok durumu:"]
688
+ for warehouse, variants in stock_info.items():
689
+ if isinstance(variants, list):
690
+ prompt_lines.append(f"- {warehouse}:")
691
+ for variant in variants:
692
+ prompt_lines.append(f" • {variant}")
693
+ else:
694
+ prompt_lines.append(f"- {warehouse}: {variants}")
695
+
696
+ # Güvenlik: Toplam adet bilgisi gösterme
697
+ if total_stock > 0:
698
+ prompt_lines.append(f"✓ Ürün stokta mevcut")
699
+
700
+ result = "\n".join(prompt_lines)
701
+
702
+ # Sonucu cache'e kaydet
703
+ stock_cache[cache_key] = (result, current_time)
704
+
705
+ return result
706
+
707
+ except Exception as e:
708
+ logger.error(f"API hatası: {e}")
709
+ return None
710
+
711
+ def is_stock_query(message):
712
+ """Mesajın stok sorgusu olup olmadığını kontrol et"""
713
+ stock_keywords = ['stok', 'stock', 'var mı', 'mevcut mu', 'kaç adet',
714
+ 'kaç tane', 'bulunuyor mu', 'hangi mağaza',
715
+ 'nerede var', 'beden', 'numara']
716
+ message_lower = message.lower()
717
+ return any(keyword in message_lower for keyword in stock_keywords)
718
+
719
+ # Sistem mesajları - Modüler prompts'tan yükle
720
+ def get_system_messages():
721
+ return get_prompt_content_only() # prompts.py'dan yükle
722
+
723
+ # ===============================
724
+ # SOHBET HAFIZASI SİSTEMİ
725
+ # ===============================
726
+
727
+ # Sohbet hafızası için basit bir dictionary
728
+ conversation_memory = {}
729
+
730
+ def get_conversation_context(phone_number):
731
+ """Kullanıcının sohbet geçmişini getir"""
732
+ if phone_number not in conversation_memory:
733
+ conversation_memory[phone_number] = {
734
+ "messages": [],
735
+ "current_category": None,
736
+ "last_activity": None
737
+ }
738
+ return conversation_memory[phone_number]
739
+
740
+ def add_to_conversation(phone_number, user_message, ai_response):
741
+ """Sohbet geçmişine ekle"""
742
+ import datetime
743
+
744
+ context = get_conversation_context(phone_number)
745
+ context["last_activity"] = datetime.datetime.now()
746
+
747
+ context["messages"].append({
748
+ "user": user_message,
749
+ "ai": ai_response,
750
+ "timestamp": datetime.datetime.now()
751
+ })
752
+
753
+ # Sadece son 10 mesajı tut
754
+ if len(context["messages"]) > 10:
755
+ context["messages"] = context["messages"][-10:]
756
+
757
+ detect_category(phone_number, user_message, ai_response)
758
+
759
+ def detect_category(phone_number, user_message, ai_response):
760
+ """Konuşulan kategoriyi tespit et"""
761
+ context = get_conversation_context(phone_number)
762
+
763
+ categories = {
764
+ "marlin": ["marlin", "marlin+"],
765
+ "madone": ["madone"],
766
+ "emonda": ["emonda", "émonda"],
767
+ "domane": ["domane"],
768
+ "checkpoint": ["checkpoint"],
769
+ "fuel": ["fuel", "fuel ex", "fuel exe"],
770
+ "procaliber": ["procaliber"],
771
+ "supercaliber": ["supercaliber"],
772
+ "fx": ["fx"],
773
+ "ds": ["ds", "dual sport"],
774
+ "powerfly": ["powerfly"],
775
+ "rail": ["rail"],
776
+ "verve": ["verve"],
777
+ "townie": ["townie"]
778
+ }
779
+
780
+ user_lower = user_message.lower()
781
+ for category, keywords in categories.items():
782
+ for keyword in keywords:
783
+ if keyword in user_lower:
784
+ context["current_category"] = category
785
+ return category
786
+
787
+ return context.get("current_category")
788
+
789
+ def build_context_messages(phone_number, current_message):
790
+ """Sohbet geçmişi ile sistem mesajlarını oluştur"""
791
+ context = get_conversation_context(phone_number)
792
+ system_messages = get_system_messages()
793
+
794
+ # Mevcut kategori varsa, sistem mesajına ekle
795
+ if context.get("current_category"):
796
+ 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."
797
+ system_messages.append({"role": "system", "content": category_msg})
798
+
799
+ # Son 3 mesaj alışverişini ekle
800
+ recent_messages = context["messages"][-3:] if context["messages"] else []
801
+
802
+ all_messages = system_messages.copy()
803
+
804
+ # Geçmiş mesajları ekle
805
+ for msg in recent_messages:
806
+ all_messages.append({"role": "user", "content": msg["user"]})
807
+ all_messages.append({"role": "assistant", "content": msg["ai"]})
808
+
809
+ # Mevcut mesajı ekle
810
+ all_messages.append({"role": "user", "content": current_message})
811
+
812
+ return all_messages
813
+
814
+ def process_whatsapp_message_with_media(user_message, phone_number, media_urls, media_types):
815
+ """Medya içeriği olan WhatsApp mesajı işleme - GPT-5 Vision ile"""
816
+ try:
817
+ logger.info(f"🖼️ Medya analizi başlıyor: {len(media_urls)} medya")
818
+
819
+ # Pasif profil analizi
820
+ profile_analysis = analyze_user_message(phone_number, user_message)
821
+ logger.info(f"📊 Profil analizi: {phone_number} -> {profile_analysis}")
822
+
823
+ # Sohbet geçmişi ile sistem mesajlarını oluştur
824
+ messages = build_context_messages(phone_number, user_message if user_message else "Gönderilen görseli analiz et")
825
+
826
+ # GPT-5 Vision için mesaj hazırla
827
+ vision_message = {
828
+ "role": "user",
829
+ "content": []
830
+ }
831
+
832
+ # Metin mesajı varsa ekle
833
+ if user_message and user_message.strip():
834
+ vision_message["content"].append({
835
+ "type": "text",
836
+ "text": user_message
837
+ })
838
+ else:
839
+ vision_message["content"].append({
840
+ "type": "text",
841
+ "text": "Bu görselde ne var? Detaylı açıkla."
842
+ })
843
+
844
+ # Medya URL'lerini ekle (Twilio medya URL'leri için proxy kullan)
845
+ for i, media_url in enumerate(media_urls):
846
+ media_type = media_types[i] if i < len(media_types) else "image/jpeg"
847
+
848
+ # Sadece görsel medyaları işle
849
+ if media_type and media_type.startswith('image/'):
850
+ # Twilio medya URL'sini proxy üzerinden çevir
851
+ if 'api.twilio.com' in media_url:
852
+ # URL'den message SID ve media SID'yi çıkar
853
+ import re
854
+ match = re.search(r'/Messages/([^/]+)/Media/([^/]+)', media_url)
855
+ if match:
856
+ message_sid = match.group(1)
857
+ media_sid = match.group(2)
858
+ # Proxy URL'sini oluştur
859
+ proxy_url = f"https://video.trek-turkey.com/twilio-media-proxy.php?action=media&message={message_sid}&media={media_sid}"
860
+ logger.info(f"🔄 Proxy URL: {proxy_url}")
861
+
862
+ vision_message["content"].append({
863
+ "type": "image_url",
864
+ "image_url": {
865
+ "url": proxy_url
866
+ }
867
+ })
868
+ else:
869
+ # Diğer URL'leri doğrudan kullan
870
+ vision_message["content"].append({
871
+ "type": "image_url",
872
+ "image_url": {
873
+ "url": media_url
874
+ }
875
+ })
876
+
877
+ # Son user mesajını vision mesajıyla değiştir
878
+ messages = [msg for msg in messages if msg.get("role") != "user" or msg != messages[-1]]
879
+ messages.append(vision_message)
880
+
881
+ # Sistem mesajına bisiklet tanıma talimatı ekle
882
+ messages.insert(0, {
883
+ "role": "system",
884
+ "content": "Gönderilen görsellerde bisiklet veya bisiklet parçaları varsa, bunları detaylıca tanımla. Marka, model, renk, özellikler gibi detayları belirt. Eğer Trek bisiklet ise modeli tahmin etmeye çalış. Stok durumu sorulursa, görseldeki bisikletin özelliklerini belirterek stok kontrolü yapılması gerektiğini söyle."
885
+ })
886
+
887
+ if not OPENAI_API_KEY:
888
+ return "OpenAI API anahtarı eksik. Lütfen environment variables'ları kontrol edin."
889
+
890
+ logger.info(f"📤 GPT-5 Vision'a gönderiliyor: {len(messages)} mesaj")
891
+
892
+ payload = {
893
+ "model": "gpt-5-chat-latest",
894
+ "messages": messages,
895
+ "temperature": 0, # Deterministik cevaplar için
896
+ "max_tokens": 800,
897
+ "stream": False,
898
+ "top_p": 0.1, # Daha tutarlı cevaplar için düşük değer
899
+ "frequency_penalty": 0.1, # Tekrarları azaltmak için
900
+ "presence_penalty": 0 # Yeni konulara açık olması için
901
+ }
902
+
903
+ headers = {
904
+ "Content-Type": "application/json",
905
+ "Authorization": f"Bearer {OPENAI_API_KEY}"
906
+ }
907
+
908
+ response = requests.post(API_URL, headers=headers, json=payload)
909
+ if response.status_code == 200:
910
+ result = response.json()
911
+ ai_response = result['choices'][0]['message']['content']
912
+
913
+ # WhatsApp için formatla
914
+ formatted_response = extract_product_info_whatsapp(ai_response)
915
+
916
+ # Sohbet geçmişine ekle
917
+ add_to_conversation(phone_number, f"[Görsel gönderildi] {user_message if user_message else ''}", formatted_response)
918
+
919
+ return formatted_response
920
+ else:
921
+ logger.error(f"OpenAI API Error: {response.status_code} - {response.text}")
922
+ return f"Görsel analizi başarısız oldu. Lütfen tekrar deneyin."
923
+
924
+ except Exception as e:
925
+ logger.error(f"❌ Medya işleme hatası: {e}")
926
+ import traceback
927
+ traceback.print_exc()
928
+ return "Görsel işlenirken bir hata oluştu. Lütfen tekrar deneyin."
929
+
930
+ def process_whatsapp_message_with_memory(user_message, phone_number):
931
+ """Hafızalı WhatsApp mesaj işleme"""
932
+ try:
933
+ # 🔔 Yeni Mağaza Bildirim Sistemi - Mehmet Bey'e otomatik bildirim
934
+ if USE_STORE_NOTIFICATION:
935
+ # Önce basit keyword kontrolü yap
936
+ should_notify_mehmet, notification_reason, urgency = should_notify_mehmet_bey(user_message)
937
+
938
+ # Eğer keyword'le yakalanmadıysa ve Intent Analyzer varsa, onu da kontrol et
939
+ if not should_notify_mehmet and USE_INTENT_ANALYZER:
940
+ context = get_conversation_context(phone_number)
941
+ intent_analysis = analyze_customer_intent(user_message, context)
942
+ should_notify_mehmet, notification_reason, urgency = should_notify_mehmet_bey(user_message, intent_analysis)
943
+ else:
944
+ intent_analysis = None
945
+
946
+ if should_notify_mehmet:
947
+ # Ürün bilgisini belirle
948
+ if intent_analysis:
949
+ product = intent_analysis.get("product") or "Belirtilmemiş"
950
+ else:
951
+ # Basit keyword'den ürün çıkar
952
+ context = get_conversation_context(phone_number)
953
+ product = context.get("current_category") or "Ürün belirtilmemiş"
954
+
955
+ # Bildirim tipini belirle
956
+ if "rezervasyon" in notification_reason.lower() or urgency == "high":
957
+ action = "reserve"
958
+ elif "mağaza" in notification_reason.lower() or "lokasyon" in notification_reason.lower():
959
+ action = "info"
960
+ elif "fiyat" in notification_reason.lower() or "ödeme" in notification_reason.lower():
961
+ action = "price"
962
+ else:
963
+ action = "info"
964
+
965
+ # Detaylı bilgi mesajı
966
+ additional_info = f"{notification_reason}\n\nMüşteri Mesajı: '{user_message}'"
967
+ if urgency == "high":
968
+ additional_info = "⚠️ YÜKSEK ÖNCELİK ⚠️\n" + additional_info
969
+
970
+ # Bildirim gönder
971
+ result = send_store_notification(
972
+ customer_phone=phone_number,
973
+ customer_name=None,
974
+ product_name=product,
975
+ action=action,
976
+ store_name=None,
977
+ additional_info=additional_info
978
+ )
979
+
980
+ if result:
981
+ logger.info(f"✅ Mehmet Bey'e bildirim gönderildi!")
982
+ logger.info(f" 📍 Sebep: {notification_reason}")
983
+ logger.info(f" ⚡ Öncelik: {urgency}")
984
+ logger.info(f" 📦 Ürün: {product}")
985
+ else:
986
+ logger.error("❌ Mehmet Bey'e bildirim gönderilemedi")
987
+
988
+
989
+ # 🧠 Pasif profil analizi - kullanıcı mesajını analiz et
990
+ profile_analysis = analyze_user_message(phone_number, user_message)
991
+ logger.info(f"📊 Profil analizi: {phone_number} -> {profile_analysis}")
992
+
993
+ # 🎯 Kişiselleştirilmiş öneriler kontrolü
994
+ if any(keyword in user_message.lower() for keyword in ["öneri", "öner", "tavsiye", "ne önerirsin"]):
995
+ personalized = get_personalized_recommendations(phone_number, products)
996
+ if personalized.get("personalized") and personalized.get("recommendations"):
997
+ # Kullanıcı profiline göre özelleştirilmiş cevap hazırla
998
+ profile_summary = get_user_profile_summary(phone_number)
999
+ custom_response = create_personalized_response(personalized, profile_summary)
1000
+ return extract_product_info_whatsapp(custom_response)
1001
+
1002
+ # Enhanced features kaldırıldı - GPT-4 doğal dil anlama kullanacak
1003
+
1004
+ # Sohbet geçmişi ile sistem mesajlarını oluştur
1005
+ messages = build_context_messages(phone_number, user_message)
1006
+
1007
+ # 🎯 Profil bilgilerini sistem mesajlarına ekle
1008
+ profile_summary = get_user_profile_summary(phone_number)
1009
+ if profile_summary.get("exists") and profile_summary.get("confidence", 0) > 0.3:
1010
+ profile_context = create_profile_context_message(profile_summary)
1011
+ messages.append({"role": "system", "content": profile_context})
1012
+
1013
+ # 🔍 BF Space: Use improved product search if available
1014
+ product_found_improved = False
1015
+ if USE_IMPROVED_SEARCH and improved_whatsapp_bot:
1016
+ try:
1017
+ product_result = improved_whatsapp_bot.process_message(user_message)
1018
+ if product_result['is_product_query'] and product_result['response']:
1019
+ # Check if user is asking about specific warehouse/store location
1020
+ if any(keyword in user_message.lower() for keyword in ['mağaza', 'mağazada', 'nerede', 'hangi mağaza', 'şube']):
1021
+ # First, always search for products using improved search
1022
+ # This will find products even with partial/typo names
1023
+ warehouse_info_parts = []
1024
+
1025
+ # Use the response text from improved search to extract product names
1026
+ if product_result['response']:
1027
+ # Extract product names from the response
1028
+ import re
1029
+ # Look for product names in bold (between * markers)
1030
+ product_names = re.findall(r'\*([^*]+)\*', product_result['response'])
1031
+
1032
+ if product_names:
1033
+ for product_name in product_names[:3]: # Max 3 products
1034
+ # Clean up the product name
1035
+ product_name = product_name.strip()
1036
+
1037
+ # Remove numbering like "1." "2." from the beginning
1038
+ import re
1039
+ product_name = re.sub(r'^\d+\.\s*', '', product_name)
1040
+
1041
+ # Skip status indicators
1042
+ if product_name in ['Stokta mevcut', 'Stokta yok', 'Fiyat:', 'Kampanya:', 'İndirim:', 'Birden fazla ürün buldum:']:
1043
+ continue
1044
+
1045
+ warehouse_stock = get_warehouse_stock(product_name)
1046
+
1047
+ if warehouse_stock and warehouse_stock != ['Ürün bulunamadı'] and warehouse_stock != ['Hiçbir mağazada stokta bulunmuyor']:
1048
+ warehouse_info_parts.append(f"{product_name} mağaza stogu:")
1049
+ warehouse_info_parts.extend(warehouse_stock)
1050
+ warehouse_info_parts.append("")
1051
+ break # Found warehouse info, stop searching
1052
+
1053
+ # If still no warehouse info, use products_found as backup
1054
+ if not warehouse_info_parts and product_result['products_found']:
1055
+ for product in product_result['products_found'][:2]:
1056
+ product_name = product[2] # Full product name
1057
+ warehouse_stock = get_warehouse_stock(product_name)
1058
+
1059
+ if warehouse_stock and warehouse_stock != ['Ürün bulunamadı'] and warehouse_stock != ['Hiçbir mağazada stokta bulunmuyor']:
1060
+ warehouse_info_parts.append(f"{product_name} mağaza stogu:")
1061
+ warehouse_info_parts.extend(warehouse_stock)
1062
+ warehouse_info_parts.append("")
1063
+ break
1064
+
1065
+ if warehouse_info_parts:
1066
+ warehouse_response = "\n".join(warehouse_info_parts)
1067
+ messages.append({
1068
+ "role": "system",
1069
+ "content": f"MAĞAZA STOK BİLGİSİ (BF Space):\n{warehouse_response}\n\nSADECE bu bilgileri kullanarak kullanıcıya yardımcı ol."
1070
+ })
1071
+ product_found_improved = True
1072
+ logger.info("✅ BF Space: Warehouse stock info used")
1073
+
1074
+ if not product_found_improved:
1075
+ # Use improved search response directly
1076
+ messages.append({
1077
+ "role": "system",
1078
+ "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."
1079
+ })
1080
+ product_found_improved = True
1081
+ logger.info("✅ BF Space: Improved product search used")
1082
+ except Exception as e:
1083
+ logger.error(f"❌ BF Space: Improved search error: {e}")
1084
+
1085
+ # Fallback to warehouse search if improved search didn't work
1086
+ if not product_found_improved:
1087
+ # Check if message seems to be asking about products
1088
+ product_keywords = ['fiyat', 'kaç', 'stok', 'var mı', 'mevcut', 'bisiklet', 'bike',
1089
+ 'trek', 'model', 'beden', 'renk', 'mağaza', 'nerede', 'hangi']
1090
+
1091
+ # Common non-product responses
1092
+ non_product_responses = ['süper', 'harika', 'güzel', 'teşekkür', 'tamam', 'olur',
1093
+ 'evet', 'hayır', 'peki', 'anladım', 'tamamdır']
1094
+
1095
+ is_product_query = False
1096
+ lower_message = user_message.lower()
1097
+
1098
+ # Check if it's likely a product query
1099
+ if any(keyword in lower_message for keyword in product_keywords):
1100
+ is_product_query = True
1101
+ # Check if it's NOT a simple response
1102
+ elif lower_message not in non_product_responses and len(lower_message.split()) > 1:
1103
+ # Multi-word queries might be product searches
1104
+ is_product_query = True
1105
+ # Single short words are usually not products
1106
+ elif len(lower_message.split()) == 1 and len(lower_message) < 6:
1107
+ is_product_query = False
1108
+
1109
+ if is_product_query:
1110
+ # Use GPT-5 warehouse search for product queries
1111
+ logger.info("📦 Using GPT-5 warehouse search")
1112
+ warehouse_result = get_warehouse_stock(user_message)
1113
+ if warehouse_result and warehouse_result != ['Ürün bulunamadı']:
1114
+ warehouse_response = "\n".join(warehouse_result)
1115
+ messages.append({
1116
+ "role": "system",
1117
+ "content": f"MAĞAZA STOK BİLGİSİ:\n{warehouse_response}\n\nBu bilgileri kullanarak kullanıcıya yardımcı ol. Fiyat ve stok bilgilerini AYNEN kullan."
1118
+ })
1119
+ logger.info(f"✅ Warehouse stock info added: {warehouse_response[:200]}...")
1120
+ else:
1121
+ logger.info(f"🚫 Skipping product search for: '{user_message}'")
1122
+
1123
+ if not OPENAI_API_KEY:
1124
+ return "OpenAI API anahtarı eksik. Lütfen environment variables'ları kontrol edin."
1125
+
1126
+ # Debug: Log what we're sending to GPT
1127
+ logger.info(f"📤 Sending to GPT-5: {len(messages)} messages")
1128
+ for i, msg in enumerate(messages):
1129
+ if msg.get('role') == 'system':
1130
+ content_preview = msg.get('content', '')[:500]
1131
+ if 'Fiyat:' in content_preview or 'TL' in content_preview:
1132
+ logger.info(f"💰 System message with price info: {content_preview}")
1133
+
1134
+ payload = {
1135
+ "model": "gpt-5-chat-latest",
1136
+ "messages": messages,
1137
+ "temperature": 0, # Deterministik cevaplar için
1138
+ "max_tokens": 800,
1139
+ "stream": False,
1140
+ "top_p": 0.1, # Daha tutarlı cevaplar için düşük değer
1141
+ "frequency_penalty": 0.1, # Tekrarları azaltmak için
1142
+ "presence_penalty": 0 # Yeni konulara açık olması için
1143
+ }
1144
+
1145
+ headers = {
1146
+ "Content-Type": "application/json",
1147
+ "Authorization": f"Bearer {OPENAI_API_KEY}"
1148
+ }
1149
+
1150
+ response = requests.post(API_URL, headers=headers, json=payload)
1151
+ if response.status_code == 200:
1152
+ result = response.json()
1153
+ ai_response = result['choices'][0]['message']['content']
1154
+
1155
+ # WhatsApp için resim URL'lerini formatla
1156
+ formatted_response = extract_product_info_whatsapp(ai_response)
1157
+
1158
+ # Sohbet geçmişine ekle
1159
+ add_to_conversation(phone_number, user_message, formatted_response)
1160
+
1161
+ return formatted_response
1162
+ else:
1163
+ print(f"OpenAI API Error: {response.status_code} - {response.text}")
1164
+ return f"API hatası: {response.status_code}. Lütfen daha sonra tekrar deneyin."
1165
+
1166
+ except Exception as e:
1167
+ print(f"❌ WhatsApp mesaj işleme hatası: {e}")
1168
+ import traceback
1169
+ traceback.print_exc()
1170
+ logger.error(f"Detailed error: {str(e)}")
1171
+ logger.error(f"Error type: {type(e).__name__}")
1172
+ return "Teknik bir sorun oluştu. Lütfen daha sonra tekrar deneyin."
1173
+
1174
+ def create_profile_context_message(profile_summary):
1175
+ """Profil bilgilerini sistem mesajına çevir"""
1176
+ context_parts = []
1177
+
1178
+ preferences = profile_summary.get("preferences", {})
1179
+ behavior = profile_summary.get("behavior", {})
1180
+
1181
+ # Bütçe bilgisi
1182
+ if preferences.get("budget_min") and preferences.get("budget_max"):
1183
+ budget_min = preferences["budget_min"]
1184
+ budget_max = preferences["budget_max"]
1185
+ context_parts.append(f"Kullanıcının bütçesi: {budget_min:,}-{budget_max:,} TL")
1186
+
1187
+ # Kategori tercihleri
1188
+ if preferences.get("categories"):
1189
+ categories = ", ".join(preferences["categories"])
1190
+ context_parts.append(f"İlgilendiği kategoriler: {categories}")
1191
+
1192
+ # Kullanım amacı
1193
+ if preferences.get("usage_purpose"):
1194
+ purposes = ", ".join(preferences["usage_purpose"])
1195
+ context_parts.append(f"Kullanım amacı: {purposes}")
1196
+
1197
+ # Davranış kalıpları
1198
+ if behavior.get("price_sensitive"):
1199
+ context_parts.append("Fiyata duyarlı bir kullanıcı")
1200
+ if behavior.get("tech_interested"):
1201
+ context_parts.append("Teknik detaylarla ilgilenen bir kullanıcı")
1202
+ if behavior.get("comparison_lover"):
1203
+ context_parts.append("Karşılaştırma yapmayı seven bir kullanıcı")
1204
+
1205
+ # Etkileşim stili
1206
+ interaction_style = profile_summary.get("interaction_style", "balanced")
1207
+ style_descriptions = {
1208
+ "analytical": "Detaylı ve analitik bilgi bekleyen",
1209
+ "budget_conscious": "Bütçe odaklı ve ekonomik seçenekleri arayan",
1210
+ "technical": "Teknik özellikler ve spesifikasyonlarla ilgilenen",
1211
+ "decisive": "Hızlı karar veren ve özet bilgi isteyen",
1212
+ "balanced": "Dengeli yaklaşım sergileyen"
1213
+ }
1214
+ context_parts.append(f"{style_descriptions.get(interaction_style, 'balanced')} bir kullanıcı")
1215
+
1216
+ if context_parts:
1217
+ return f"Kullanıcı profili: {'. '.join(context_parts)}. Bu bilgileri göz önünde bulundurarak cevap ver."
1218
+ return ""
1219
+
1220
+ def create_personalized_response(personalized_data, profile_summary):
1221
+ """Kişiselleştirilmiş öneri cevabı oluştur"""
1222
+ response_parts = []
1223
+
1224
+ # Kullanıcı stiline göre selamlama
1225
+ interaction_style = profile_summary.get("interaction_style", "balanced")
1226
+ if interaction_style == "analytical":
1227
+ response_parts.append("🔍 Profilinizi analiz ederek sizin için en uygun seçenekleri belirledim:")
1228
+ elif interaction_style == "budget_conscious":
1229
+ response_parts.append("💰 Bütçenize uygun en iyi seçenekleri hazırladım:")
1230
+ elif interaction_style == "technical":
1231
+ response_parts.append("⚙️ Teknik tercihlerinize göre önerilerim:")
1232
+ else:
1233
+ response_parts.append("🎯 Size özel seçtiklerim:")
1234
+
1235
+ # Önerileri listele
1236
+ recommendations = personalized_data.get("recommendations", [])[:3] # İlk 3 öneri
1237
+
1238
+ if recommendations:
1239
+ response_parts.append("\n")
1240
+ for i, product in enumerate(recommendations, 1):
1241
+ name, item_info, full_name = product
1242
+ price = item_info[1] if len(item_info) > 1 else "Fiyat yok"
1243
+ response_parts.append(f"**{i}. {full_name}**")
1244
+ response_parts.append(f"💰 Fiyat: {price} TL")
1245
+ response_parts.append("")
1246
+
1247
+ # Profil bazlı açıklama
1248
+ preferences = profile_summary.get("preferences", {})
1249
+ if preferences.get("categories"):
1250
+ category = preferences["categories"][0]
1251
+ response_parts.append(f"Bu öneriler {category} kategorisindeki ilginizi ve tercihlerinizi dikkate alarak hazırlandı.")
1252
+
1253
+ return "\n".join(response_parts)
1254
+
1255
+ def split_long_message(message, max_length=1600):
1256
+ """Uzun mesajları WhatsApp için uygun parçalara böler"""
1257
+ if len(message) <= max_length:
1258
+ return [message]
1259
+
1260
+ parts = []
1261
+ remaining = message
1262
+
1263
+ while len(remaining) > max_length:
1264
+ cut_point = max_length
1265
+
1266
+ # Geriye doğru git ve uygun kesme noktası ara
1267
+ for i in range(max_length, max_length - 200, -1):
1268
+ if i < len(remaining) and remaining[i] in ['.', '!', '?', '\n']:
1269
+ cut_point = i + 1
1270
+ break
1271
+ elif i < len(remaining) and remaining[i] in [' ', ',', ';']:
1272
+ cut_point = i
1273
+
1274
+ parts.append(remaining[:cut_point].strip())
1275
+ remaining = remaining[cut_point:].strip()
1276
+
1277
+ if remaining:
1278
+ parts.append(remaining)
1279
+
1280
+ return parts
1281
+
1282
+ # ===============================
1283
+ # HAFIZA SİSTEMİ SONU
1284
+ # ===============================
1285
+
1286
+ # WhatsApp mesajı işleme (eski fonksiyon - yedek için)
1287
+ def process_whatsapp_message(user_message):
1288
+ try:
1289
+ system_messages = get_system_messages()
1290
+
1291
+ # 🔍 BF Space: Use improved product search if available (backup function)
1292
+ product_found_improved = False
1293
+ if USE_IMPROVED_SEARCH and improved_whatsapp_bot:
1294
+ try:
1295
+ product_result = improved_whatsapp_bot.process_message(user_message)
1296
+ if product_result['is_product_query'] and product_result['response']:
1297
+ # Check if user is asking about specific warehouse/store location
1298
+ if any(keyword in user_message.lower() for keyword in ['mağaza', 'mağazada', 'nerede', 'hangi mağaza', 'şube']):
1299
+ # Get warehouse stock info for the found products
1300
+ if product_result['products_found']:
1301
+ warehouse_info_parts = []
1302
+ for product in product_result['products_found'][:2]: # Max 2 products
1303
+ product_name = product[2] # Full product name
1304
+ warehouse_stock = get_warehouse_stock(product_name)
1305
+ if warehouse_stock:
1306
+ warehouse_info_parts.append(f"{product_name} mağaza stogu:")
1307
+ warehouse_info_parts.extend(warehouse_stock)
1308
+ warehouse_info_parts.append("")
1309
+
1310
+ if warehouse_info_parts:
1311
+ warehouse_response = "\n".join(warehouse_info_parts)
1312
+ system_messages.append({
1313
+ "role": "system",
1314
+ "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."
1315
+ })
1316
+ product_found_improved = True
1317
+
1318
+ if not product_found_improved:
1319
+ system_messages.append({
1320
+ "role": "system",
1321
+ "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."
1322
+ })
1323
+ product_found_improved = True
1324
+ except Exception as e:
1325
+ logger.error(f"BF Space backup: Improved search error: {e}")
1326
+
1327
+ # Fallback to basic search
1328
+ if not product_found_improved:
1329
+ # Ürün bilgilerini kontrol et (basic search)
1330
+ input_words = user_message.lower().split()
1331
+ for product_info in products:
1332
+ if product_info[0] in input_words:
1333
+ if product_info[1][0] == "stokta":
1334
+ normal_price = f"Fiyat: {product_info[1][1]} TL"
1335
+ if product_info[1][3]:
1336
+ eft_price = f"Havale: {product_info[1][3]} TL"
1337
+ price_info = f"{normal_price}, {eft_price}"
1338
+ else:
1339
+ price_info = normal_price
1340
+
1341
+ new_msg = f"{product_info[2]} {product_info[1][0]} - {price_info}"
1342
+ else:
1343
+ new_msg = f"{product_info[2]} {product_info[1][0]}"
1344
+ system_messages.append({"role": "system", "content": new_msg})
1345
+ break
1346
+
1347
+ messages = system_messages + [{"role": "user", "content": user_message}]
1348
+
1349
+ if not OPENAI_API_KEY:
1350
+ return "OpenAI API anahtarı eksik. Lütfen environment variables'ları kontrol edin."
1351
+
1352
+ payload = {
1353
+ "model": "gpt-5-chat-latest",
1354
+ "messages": messages,
1355
+ "temperature": 0, # Deterministik cevaplar için
1356
+ "max_tokens": 800,
1357
+ "stream": False,
1358
+ "top_p": 0.1, # Daha tutarlı cevaplar için düşük değer
1359
+ "frequency_penalty": 0.1, # Tekrarları azaltmak için
1360
+ "presence_penalty": 0 # Yeni konulara açık olması için
1361
+ }
1362
+
1363
+ headers = {
1364
+ "Content-Type": "application/json",
1365
+ "Authorization": f"Bearer {OPENAI_API_KEY}"
1366
+ }
1367
+
1368
+ response = requests.post(API_URL, headers=headers, json=payload)
1369
+ if response.status_code == 200:
1370
+ result = response.json()
1371
+ return result['choices'][0]['message']['content']
1372
+ else:
1373
+ print(f"OpenAI API Error: {response.status_code} - {response.text}")
1374
+ return f"API hatası: {response.status_code}. Lütfen daha sonra tekrar deneyin."
1375
+
1376
+ except Exception as e:
1377
+ print(f"❌ WhatsApp mesaj işleme hatası: {e}")
1378
+ import traceback
1379
+ traceback.print_exc()
1380
+ logger.error(f"Detailed error: {str(e)}")
1381
+ logger.error(f"Error type: {type(e).__name__}")
1382
+ return "Teknik bir sorun oluştu. Lütfen daha sonra tekrar deneyin."
1383
+
1384
+ # FastAPI uygulaması
1385
+ app = FastAPI()
1386
+
1387
+ @app.post("/whatsapp-webhook")
1388
+ async def whatsapp_webhook(request: Request):
1389
+ try:
1390
+ form_data = await request.form()
1391
+
1392
+ from_number = form_data.get('From')
1393
+ to_number = form_data.get('To')
1394
+ message_body = form_data.get('Body')
1395
+ message_status = form_data.get('MessageStatus')
1396
+
1397
+ # Medya içeriği kontrolü
1398
+ num_media = form_data.get('NumMedia', '0')
1399
+ media_urls = []
1400
+ media_types = []
1401
+
1402
+ # Medya varsa URL'leri topla
1403
+ if num_media and int(num_media) > 0:
1404
+ for i in range(int(num_media)):
1405
+ media_url = form_data.get(f'MediaUrl{i}')
1406
+ media_type = form_data.get(f'MediaContentType{i}')
1407
+ if media_url:
1408
+ media_urls.append(media_url)
1409
+ media_types.append(media_type)
1410
+ logger.info(f"📸 Medya alındı: {media_type} - {media_url[:100]}...")
1411
+
1412
+ print(f"📱 Webhook - From: {from_number}, Body: {message_body}, Status: {message_status}")
1413
+
1414
+ # Durum güncellemelerini ignore et
1415
+ if message_status in ['sent', 'delivered', 'read', 'failed']:
1416
+ return {"status": "ignored", "message": f"Status: {message_status}"}
1417
+
1418
+ # Giden mesajları ignore et
1419
+ if to_number != TWILIO_WHATSAPP_NUMBER:
1420
+ return {"status": "ignored", "message": "Outgoing message"}
1421
+
1422
+ # Media Queue V2 İşleme
1423
+ if USE_MEDIA_QUEUE:
1424
+ if media_urls:
1425
+ # Medya mesajı geldi
1426
+ logger.info(f"📸 Media Queue V2: Medya alındı - {from_number}")
1427
+
1428
+ # Media Queue'ya ekle ve bekleme mesajı al
1429
+ wait_message = media_queue.handle_media(
1430
+ from_number,
1431
+ media_urls,
1432
+ media_types,
1433
+ message_body or ""
1434
+ )
1435
+
1436
+ # Bekleme mesajını gönder
1437
+ if twilio_client:
1438
+ twilio_client.messages.create(
1439
+ messaging_service_sid=TWILIO_MESSAGING_SERVICE_SID,
1440
+ body=wait_message,
1441
+ to=from_number
1442
+ )
1443
+ logger.info(f"📤 Bekleme mesajı gönderildi: {wait_message}")
1444
+
1445
+ return {"status": "media_queued", "message": wait_message}
1446
+
1447
+ else:
1448
+ # Metin mesajı geldi - cache'de medya var mı kontrol et
1449
+ combined_text, cached_media_urls, cached_media_types = media_queue.handle_text(from_number, message_body)
1450
+
1451
+ if combined_text and cached_media_urls:
1452
+ # Medya + metin birleştirildi
1453
+ logger.info(f"✅ Media Queue V2: Birleştirildi - {from_number}")
1454
+ logger.info(f" Birleşik mesaj: {combined_text[:100]}...")
1455
+ logger.info(f" Medya sayısı: {len(cached_media_urls)}")
1456
+
1457
+ # Birleştirilmiş mesajı işle
1458
+ message_body = combined_text
1459
+ media_urls = cached_media_urls
1460
+ media_types = cached_media_types
1461
+ # Aşağıdaki normal akışa devam et
1462
+ else:
1463
+ # Normal metin mesajı, cache'de medya yok
1464
+ logger.info(f"💬 Media Queue V2: Normal metin - {from_number}")
1465
+ # Normal akışa devam et
1466
+
1467
+ # Boş mesaj kontrolü
1468
+ if not message_body or message_body.strip() == "":
1469
+ if not media_urls: # Medya da yoksa ignore et
1470
+ return {"status": "ignored", "message": "Empty message"}
1471
+
1472
+ print(f"✅ MESAJ ALINDI: {from_number} -> {message_body}")
1473
+
1474
+ if not twilio_client:
1475
+ return {"status": "error", "message": "Twilio yapılandırması eksik"}
1476
+
1477
+ # HAFIZALİ MESAJ İŞLEME - Medya desteği ile
1478
+ if media_urls:
1479
+ # Medya varsa, görsel analiz yap
1480
+ ai_response = process_whatsapp_message_with_media(message_body, from_number, media_urls, media_types)
1481
+ else:
1482
+ # Normal metin mesajı işle
1483
+ ai_response = process_whatsapp_message_with_memory(message_body, from_number)
1484
+
1485
+ # Mesajı parçalara böl
1486
+ message_parts = split_long_message(ai_response, max_length=1600)
1487
+
1488
+ sent_messages = []
1489
+
1490
+ # Her parçayı sırayla gönder
1491
+ for i, part in enumerate(message_parts):
1492
+ if len(message_parts) > 1:
1493
+ if i == 0:
1494
+ part = f"{part}\n\n(1/{len(message_parts)})"
1495
+ elif i == len(message_parts) - 1:
1496
+ part = f"({i+1}/{len(message_parts)})\n\n{part}"
1497
+ else:
1498
+ part = f"({i+1}/{len(message_parts)})\n\n{part}"
1499
+
1500
+ # WhatsApp'a gönder
1501
+ message = twilio_client.messages.create(
1502
+ messaging_service_sid=TWILIO_MESSAGING_SERVICE_SID,
1503
+ body=part,
1504
+ to=from_number
1505
+ )
1506
+
1507
+ sent_messages.append(message.sid)
1508
+
1509
+ if i < len(message_parts) - 1:
1510
+ import time
1511
+ time.sleep(0.5)
1512
+
1513
+ print(f"✅ {len(message_parts)} PARÇA GÖNDERİLDİ")
1514
+
1515
+ # Debug için mevcut kategoriyi logla
1516
+ context = get_conversation_context(from_number)
1517
+ if context.get("current_category"):
1518
+ print(f"💭 Aktif kategori: {context['current_category']}")
1519
+
1520
+ return {
1521
+ "status": "success",
1522
+ "message_parts": len(message_parts),
1523
+ "message_sids": sent_messages,
1524
+ "current_category": context.get("current_category")
1525
+ }
1526
+
1527
+ except Exception as e:
1528
+ print(f"❌ Webhook hatası: {str(e)}")
1529
+ return {"status": "error", "message": str(e)}
1530
+
1531
+ @app.get("/")
1532
+ async def root():
1533
+ return {"message": "Trek WhatsApp Bot çalışıyor!", "status": "active"}
1534
+
1535
+ # Hafızayı temizleme endpoint'i
1536
+ @app.get("/clear-memory/{phone_number}")
1537
+ async def clear_memory(phone_number: str):
1538
+ """Belirli bir telefon numarasının hafızasını temizle"""
1539
+ if phone_number in conversation_memory:
1540
+ del conversation_memory[phone_number]
1541
+ return {"status": "success", "message": f"{phone_number} hafızası temizlendi"}
1542
+ return {"status": "info", "message": "Hafıza bulunamadı"}
1543
+
1544
+ # Tüm hafızayı görme endpoint'i
1545
+ @app.get("/debug-memory")
1546
+ async def debug_memory():
1547
+ """Tüm hafızayı görüntüle (debug için)"""
1548
+ memory_info = {}
1549
+ for phone, context in conversation_memory.items():
1550
+ memory_info[phone] = {
1551
+ "current_category": context.get("current_category"),
1552
+ "message_count": len(context.get("messages", [])),
1553
+ "last_activity": str(context.get("last_activity"))
1554
+ }
1555
+ return {"conversation_memory": memory_info}
1556
+
1557
+ # Profil bilgilerini görme endpoint'i
1558
+ @app.get("/debug-profile/{phone_number}")
1559
+ async def debug_profile(phone_number: str):
1560
+ """Belirli kullanıcının profil bilgilerini görüntüle"""
1561
+ profile_summary = get_user_profile_summary(phone_number)
1562
+ return {"phone_number": phone_number, "profile": profile_summary}
1563
+
1564
+ # Tüm profilleri görme endpoint'i
1565
+ @app.get("/debug-profiles")
1566
+ async def debug_profiles():
1567
+ """Tüm kullanıcı profillerini görüntüle"""
1568
+ from whatsapp_passive_profiler import passive_profiler
1569
+ all_profiles = {}
1570
+ for phone_number in passive_profiler.profiles.keys():
1571
+ all_profiles[phone_number] = get_user_profile_summary(phone_number)
1572
+ return {"profiles": all_profiles}
1573
+
1574
+ @app.get("/health")
1575
+ async def health():
1576
+ return {
1577
+ "status": "healthy",
1578
+ "twilio_configured": twilio_client is not None,
1579
+ "openai_configured": OPENAI_API_KEY is not None,
1580
+ "products_loaded": len(products),
1581
+ "webhook_endpoint": "/whatsapp-webhook"
1582
+ }
1583
+
1584
+ @app.get("/test-madone")
1585
+ async def test_madone():
1586
+ """Test MADONE search directly"""
1587
+ from smart_warehouse_with_price import get_warehouse_stock_smart_with_price
1588
+
1589
+ # Set a test API key if needed
1590
+ import os
1591
+ if not os.getenv("OPENAI_API_KEY"):
1592
+ return {"error": "No OPENAI_API_KEY set"}
1593
+
1594
+ try:
1595
+ result = get_warehouse_stock_smart_with_price("madone sl 6")
1596
+ return {
1597
+ "query": "madone sl 6",
1598
+ "result": result if result else "No result",
1599
+ "api_key_set": bool(os.getenv("OPENAI_API_KEY"))
1600
+ }
1601
+ except Exception as e:
1602
+ return {"error": str(e), "type": type(e).__name__}
1603
+
1604
+ @app.post("/test-vision")
1605
+ async def test_vision(request: Request):
1606
+ """Test vision capabilities with a sample image URL"""
1607
+ try:
1608
+ data = await request.json()
1609
+ image_url = data.get("image_url")
1610
+ text = data.get("text", "Bu görselde ne var?")
1611
+
1612
+ if not image_url:
1613
+ return {"error": "image_url is required"}
1614
+
1615
+ # Test vision API
1616
+ messages = [
1617
+ {
1618
+ "role": "system",
1619
+ "content": "Sen bir bisiklet uzmanısın. Görselleri analiz et ve detaylı bilgi ver."
1620
+ },
1621
+ {
1622
+ "role": "user",
1623
+ "content": [
1624
+ {"type": "text", "text": text},
1625
+ {"type": "image_url", "image_url": {"url": image_url}}
1626
+ ]
1627
+ }
1628
+ ]
1629
+
1630
+ payload = {
1631
+ "model": "gpt-5-chat-latest",
1632
+ "messages": messages,
1633
+ "temperature": 0, # Deterministik cevaplar için
1634
+ "max_tokens": 500,
1635
+ "stream": False,
1636
+ "top_p": 0.1, # Daha tutarlı cevaplar için düşük değer
1637
+ "frequency_penalty": 0.1, # Tekrarları azaltmak için
1638
+ "presence_penalty": 0 # Yeni konulara açık olması için
1639
+ }
1640
+
1641
+ headers = {
1642
+ "Content-Type": "application/json",
1643
+ "Authorization": f"Bearer {OPENAI_API_KEY}"
1644
+ }
1645
+
1646
+ response = requests.post(API_URL, headers=headers, json=payload)
1647
+
1648
+ if response.status_code == 200:
1649
+ result = response.json()
1650
+ return {
1651
+ "success": True,
1652
+ "response": result['choices'][0]['message']['content'],
1653
+ "model": result.get('model', 'unknown')
1654
+ }
1655
+ else:
1656
+ return {
1657
+ "success": False,
1658
+ "error": response.text,
1659
+ "status_code": response.status_code
1660
+ }
1661
+
1662
+ except Exception as e:
1663
+ return {"error": str(e), "type": type(e).__name__}
1664
+
1665
+ if __name__ == "__main__":
1666
+ import uvicorn
1667
+ print("🚀 Trek WhatsApp Bot başlatılıyor...")
1668
+ uvicorn.run(app, host="0.0.0.0", port=7860)
app_calismiyor_20250903_2223.py ADDED
@@ -0,0 +1,1822 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import requests
4
+ import xml.etree.ElementTree as ET
5
+ import warnings
6
+ import time
7
+ import threading
8
+ from concurrent.futures import ThreadPoolExecutor, as_completed
9
+ from fastapi import FastAPI, Request
10
+ from twilio.rest import Client
11
+ from twilio.twiml.messaging_response import MessagingResponse
12
+
13
+ # Yeni modüller - Basit sistem
14
+ from prompts import get_prompt_content_only
15
+ from whatsapp_renderer import extract_product_info_whatsapp
16
+ from whatsapp_passive_profiler import (
17
+ analyze_user_message, get_user_profile_summary, get_personalized_recommendations
18
+ )
19
+
20
+ # LOGGING EN BAŞA EKLENDİ
21
+ import logging
22
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
23
+ logger = logging.getLogger(__name__)
24
+
25
+ # Import improved WhatsApp search for BF space
26
+ # DISABLED - Using GPT-5 smart warehouse search instead
27
+ USE_IMPROVED_SEARCH = False
28
+ # try:
29
+ # from whatsapp_improved_chatbot import WhatsAppImprovedChatbot
30
+ # USE_IMPROVED_SEARCH = True
31
+ # except ImportError:
32
+ # print("Improved WhatsApp chatbot not available, using basic search")
33
+ # USE_IMPROVED_SEARCH = False
34
+
35
+ # Import GPT-5 powered smart warehouse search - complete BF algorithm
36
+ try:
37
+ from smart_warehouse_with_price import get_warehouse_stock_smart_with_price
38
+ USE_GPT5_SEARCH = True
39
+ logger.info("✅ GPT-5 complete smart warehouse with price (BF algorithm) loaded")
40
+ except ImportError:
41
+ USE_GPT5_SEARCH = False
42
+ logger.info("❌ GPT-5 search not available")
43
+
44
+ warnings.simplefilter('ignore')
45
+
46
+ # Import Media Queue V2
47
+ try:
48
+ from media_queue_v2 import media_queue
49
+ USE_MEDIA_QUEUE = True
50
+ logger.info("✅ Media Queue V2 loaded successfully")
51
+ except ImportError:
52
+ USE_MEDIA_QUEUE = False
53
+ logger.info("❌ Media Queue V2 not available")
54
+
55
+ # Import Store Notification System
56
+ try:
57
+ from store_notification import (
58
+ notify_product_reservation,
59
+ notify_price_inquiry,
60
+ notify_stock_inquiry,
61
+ send_test_notification,
62
+ send_store_notification,
63
+ should_notify_mehmet_bey
64
+ )
65
+ USE_STORE_NOTIFICATION = True
66
+ logger.info("✅ Store Notification System loaded")
67
+ except ImportError:
68
+ USE_STORE_NOTIFICATION = False
69
+ logger.info("❌ Store Notification System not available")
70
+
71
+ # Import Follow-Up System
72
+ try:
73
+ from follow_up_system import (
74
+ FollowUpManager,
75
+ analyze_message_for_follow_up,
76
+ FollowUpType
77
+ )
78
+ USE_FOLLOW_UP = True
79
+ follow_up_manager = FollowUpManager()
80
+ logger.info("✅ Follow-Up System loaded")
81
+ except ImportError:
82
+ USE_FOLLOW_UP = False
83
+ follow_up_manager = None
84
+ logger.info("❌ Follow-Up System not available")
85
+
86
+ # Import Intent Analyzer
87
+ try:
88
+ from intent_analyzer import (
89
+ analyze_customer_intent,
90
+ should_notify_store,
91
+ get_smart_notification_message
92
+ )
93
+ USE_INTENT_ANALYZER = True
94
+ logger.info("✅ GPT-5 Intent Analyzer loaded")
95
+ except ImportError:
96
+ USE_INTENT_ANALYZER = False
97
+ logger.info("❌ Intent Analyzer not available")
98
+
99
+ # API ayarları
100
+ API_URL = "https://api.openai.com/v1/chat/completions"
101
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
102
+ logger.info(f"OpenAI API Key var mı: {'Evet' if OPENAI_API_KEY else 'Hayır'}")
103
+
104
+ # Twilio WhatsApp ayarları
105
+ TWILIO_ACCOUNT_SID = os.getenv("TWILIO_ACCOUNT_SID")
106
+ TWILIO_AUTH_TOKEN = os.getenv("TWILIO_AUTH_TOKEN")
107
+ TWILIO_MESSAGING_SERVICE_SID = os.getenv("TWILIO_MESSAGING_SERVICE_SID", "MG11c1dfac28ad5f81908ec9ede0f7247f")
108
+ TWILIO_WHATSAPP_NUMBER = "whatsapp:+905332047254" # Bizim WhatsApp Business numaramız
109
+
110
+ logger.info(f"Twilio SID var mı: {'Evet' if TWILIO_ACCOUNT_SID else 'Hayır'}")
111
+ logger.info(f"Twilio Auth Token var mı: {'Evet' if TWILIO_AUTH_TOKEN else 'Hayır'}")
112
+ logger.info(f"Messaging Service SID var mı: {'Evet' if TWILIO_MESSAGING_SERVICE_SID else 'Hayır'}")
113
+
114
+ if not TWILIO_ACCOUNT_SID or not TWILIO_AUTH_TOKEN:
115
+ logger.error("❌ Twilio bilgileri eksik!")
116
+ twilio_client = None
117
+ else:
118
+ try:
119
+ twilio_client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
120
+ logger.info("✅ Twilio client başarıyla oluşturuldu!")
121
+ except Exception as e:
122
+ logger.error(f"❌ Twilio client hatası: {e}")
123
+ twilio_client = None
124
+
125
+ # Mağaza stok bilgilerini çekme fonksiyonu
126
+ def get_warehouse_stock(product_name):
127
+ """B2B API'den mağaza stok bilgilerini çek - GPT-5 enhanced"""
128
+ # Try GPT-5 complete smart search (BF algorithm)
129
+ if USE_GPT5_SEARCH:
130
+ try:
131
+ gpt5_result = get_warehouse_stock_smart_with_price(product_name)
132
+ if gpt5_result and isinstance(gpt5_result, list):
133
+ # Return directly if it's already formatted strings
134
+ if all(isinstance(item, str) for item in gpt5_result):
135
+ return gpt5_result
136
+ # Format for WhatsApp if dict format
137
+ warehouse_info = []
138
+ for item in gpt5_result:
139
+ if isinstance(item, dict):
140
+ info = f"📦 {item.get('name', '')}"
141
+ if item.get('variant'):
142
+ info += f" ({item['variant']})"
143
+ if item.get('warehouses'):
144
+ info += f"\n📍 Mevcut: {', '.join(item['warehouses'])}"
145
+ if item.get('price'):
146
+ info += f"\n💰 {item['price']}"
147
+ warehouse_info.append(info)
148
+ else:
149
+ warehouse_info.append(str(item))
150
+ return warehouse_info if warehouse_info else None
151
+ except Exception as e:
152
+ logger.error(f"GPT-5 warehouse search error: {e}")
153
+ # Continue to fallback search
154
+
155
+ # Fallback to original search
156
+ try:
157
+ import re
158
+ warehouse_url = 'https://video.trek-turkey.com/bizimhesap-warehouse-xml-b2b-api-v2.php'
159
+ response = requests.get(warehouse_url, verify=False, timeout=15)
160
+
161
+ if response.status_code != 200:
162
+ return None
163
+
164
+ root = ET.fromstring(response.content)
165
+
166
+ # Turkish character normalization function
167
+ turkish_map = {'ı': 'i', 'ğ': 'g', 'ü': 'u', 'ş': 's', 'ö': 'o', 'ç': 'c', 'İ': 'i', 'I': 'i'}
168
+
169
+ def normalize_turkish(text):
170
+ import unicodedata
171
+ text = unicodedata.normalize('NFD', text)
172
+ text = ''.join(char for char in text if unicodedata.category(char) != 'Mn')
173
+ for tr_char, en_char in turkish_map.items():
174
+ text = text.replace(tr_char, en_char)
175
+ return text
176
+
177
+ # Normalize search product name
178
+ search_name = normalize_turkish(product_name.lower().strip())
179
+ search_name = search_name.replace('(2026)', '').replace('(2025)', '').replace(' gen 3', '').replace(' gen', '').strip()
180
+ search_words = search_name.split()
181
+
182
+ best_matches = []
183
+ exact_matches = []
184
+ variant_matches = []
185
+ candidates = []
186
+
187
+ # Separate size/color words from product words
188
+ size_color_words = ['s', 'm', 'l', 'xl', 'xs', 'small', 'medium', 'large',
189
+ 'turuncu', 'siyah', 'beyaz', 'mavi', 'kirmizi', 'yesil',
190
+ 'orange', 'black', 'white', 'blue', 'red', 'green']
191
+
192
+ variant_words = [word for word in search_words if word in size_color_words]
193
+ product_words = [word for word in search_words if word not in size_color_words]
194
+
195
+ # Check if this is a size/color specific query
196
+ is_size_color_query = len(variant_words) > 0 and len(search_words) <= 4
197
+
198
+ # İlk geçiş: Variant alanında beden/renk araması
199
+ if is_size_color_query:
200
+ for product in root.findall('Product'):
201
+ product_name_elem = product.find('ProductName')
202
+ variant_elem = product.find('ProductVariant')
203
+
204
+ if product_name_elem is not None and product_name_elem.text:
205
+ xml_product_name = product_name_elem.text.strip()
206
+ normalized_product_name = normalize_turkish(xml_product_name.lower())
207
+
208
+ # If there are product words, check if they match the product name
209
+ product_name_matches = True
210
+ if product_words:
211
+ product_name_matches = all(word in normalized_product_name for word in product_words)
212
+
213
+ # Only proceed if product name matches (or no product context)
214
+ if product_name_matches:
215
+ # Variant field check
216
+ if variant_elem is not None and variant_elem.text:
217
+ variant_text = normalize_turkish(variant_elem.text.lower().replace('-', ' '))
218
+
219
+ # Check if all variant words are in variant field
220
+ if all(word in variant_text for word in variant_words):
221
+ variant_matches.append((product, xml_product_name, variant_text))
222
+
223
+ if variant_matches:
224
+ candidates = variant_matches
225
+ else:
226
+ # Fallback to normal product name search
227
+ is_size_color_query = False
228
+
229
+ # İkinci geçiş: Normal ürün adı tam eşleşmeleri (variant match yoksa)
230
+ if not is_size_color_query or not candidates:
231
+ for product in root.findall('Product'):
232
+ product_name_elem = product.find('ProductName')
233
+ if product_name_elem is not None and product_name_elem.text:
234
+ xml_product_name = product_name_elem.text.strip()
235
+ normalized_xml = normalize_turkish(xml_product_name.lower())
236
+ normalized_xml = normalized_xml.replace('(2026)', '').replace('(2025)', '').replace(' gen 3', '').replace(' gen', '').strip()
237
+ xml_words = normalized_xml.split()
238
+
239
+ # Tam eşleşme kontrolü - ilk iki kelime tam aynı olmalı
240
+ if len(search_words) >= 2 and len(xml_words) >= 2:
241
+ search_key = f"{search_words[0]} {search_words[1]}"
242
+ xml_key = f"{xml_words[0]} {xml_words[1]}"
243
+
244
+ if search_key == xml_key:
245
+ exact_matches.append((product, xml_product_name, normalized_xml))
246
+
247
+ # Eğer variant match varsa onu kullan, yoksa exact matches kullan
248
+ if not candidates:
249
+ candidates = exact_matches if exact_matches else []
250
+
251
+ # Eğer hala bir match yoksa, fuzzy matching yap
252
+ if not candidates:
253
+ for product in root.findall('Product'):
254
+ product_name_elem = product.find('ProductName')
255
+ if product_name_elem is not None and product_name_elem.text:
256
+ xml_product_name = product_name_elem.text.strip()
257
+ normalized_xml = normalize_turkish(xml_product_name.lower())
258
+ normalized_xml = normalized_xml.replace('(2026)', '').replace('(2025)', '').replace(' gen 3', '').replace(' gen', '').strip()
259
+ xml_words = normalized_xml.split()
260
+
261
+ # Ortak kelime sayısını hesapla
262
+ common_words = set(search_words) & set(xml_words)
263
+
264
+ # En az 2 ortak kelime olmalı VE ilk kelime aynı olmalı (marka kontrolü)
265
+ if (len(common_words) >= 2 and
266
+ len(search_words) > 0 and len(xml_words) > 0 and
267
+ search_words[0] == xml_words[0]):
268
+ best_matches.append((product, xml_product_name, normalized_xml, len(common_words)))
269
+
270
+ # En çok ortak kelimeye sahip olanları seç
271
+ if best_matches:
272
+ max_common = max(match[3] for match in best_matches)
273
+ candidates = [(match[0], match[1], match[2]) for match in best_matches if match[3] == max_common]
274
+
275
+ # Stok bilgilerini topla ve tekrarları önle
276
+ warehouse_stock_map = {} # warehouse_name -> total_stock
277
+
278
+ for product, xml_name, _ in candidates:
279
+ # New B2B API structure: Warehouse elements are direct children of Product
280
+ for warehouse in product.findall('Warehouse'):
281
+ name_elem = warehouse.find('Name')
282
+ stock_elem = warehouse.find('Stock')
283
+
284
+ if name_elem is not None and stock_elem is not None:
285
+ warehouse_name = name_elem.text if name_elem.text else "Bilinmeyen"
286
+ try:
287
+ stock_count = int(stock_elem.text) if stock_elem.text else 0
288
+ if stock_count > 0:
289
+ # Aynı mağaza için stokları topla
290
+ if warehouse_name in warehouse_stock_map:
291
+ warehouse_stock_map[warehouse_name] += stock_count
292
+ else:
293
+ warehouse_stock_map[warehouse_name] = stock_count
294
+ except (ValueError, TypeError):
295
+ pass
296
+
297
+ if warehouse_stock_map:
298
+ # Mağaza stoklarını liste halinde döndür
299
+ all_warehouse_info = []
300
+ for warehouse_name, total_stock in warehouse_stock_map.items():
301
+ all_warehouse_info.append(f"{warehouse_name}: Stokta var")
302
+ return all_warehouse_info
303
+ else:
304
+ return ["Hiçbir mağazada stokta bulunmuyor"]
305
+
306
+ except Exception as e:
307
+ print(f"Mağaza stok bilgisi çekme hatası: {e}")
308
+ return None
309
+
310
+ # Trek bisiklet ürünlerini çekme - DÜZELTİLMİŞ VERSİYON
311
+ try:
312
+ # All XML debug prints disabled to reduce noise
313
+ url = 'https://www.trekbisiklet.com.tr/output/8582384479'
314
+ response = requests.get(url, verify=False, timeout=10)
315
+
316
+ # XML parsing - all debug prints disabled
317
+ content_preview = response.content[:500].decode('utf-8', errors='ignore')
318
+ root = ET.fromstring(response.content)
319
+ all_items = root.findall('item')
320
+
321
+ # Item analysis disabled for production
322
+
323
+ # Marlin arama testi
324
+ marlin_count = 0
325
+ products = []
326
+
327
+ for item in all_items:
328
+ # Değişkenleri önceden tanımla
329
+ stock_number = 0
330
+ stock_amount = "stokta değil"
331
+ price = ""
332
+ price_eft = ""
333
+ product_link = ""
334
+
335
+ rootlabel = item.find('rootlabel')
336
+ if rootlabel is None or not rootlabel.text:
337
+ continue
338
+
339
+ full_name = rootlabel.text.strip()
340
+ name_words = full_name.lower().split()
341
+ name = name_words[0] if name_words else "unknown"
342
+
343
+ # STOK KONTROLÜ - SAYISAL KARŞILAŞTIRMA
344
+ stock_element = item.find('stockAmount')
345
+ if stock_element is not None and stock_element.text:
346
+ try:
347
+ stock_number = int(stock_element.text.strip())
348
+ stock_amount = "stokta" if stock_number > 0 else "stokta değil"
349
+ except (ValueError, TypeError):
350
+ stock_number = 0
351
+ stock_amount = "stokta değil"
352
+
353
+ # Marlin kontrolü
354
+ if 'marlin' in full_name.lower():
355
+ marlin_count += 1
356
+ pass
357
+
358
+ # Stokta olan ürünler için fiyat bilgilerini al
359
+ if stock_amount == "stokta":
360
+ # Normal fiyat
361
+ price_element = item.find('priceTaxWithCur')
362
+ price_str = price_element.text if price_element is not None and price_element.text else "0"
363
+
364
+ # Kampanya fiyatı
365
+ price_rebate_element = item.find('priceRebateWithTax')
366
+ price_rebate_str = price_rebate_element.text if price_rebate_element is not None and price_rebate_element.text else ""
367
+
368
+ # Kampanya fiyatı varsa onu kullan, yoksa normal fiyatı kullan
369
+ final_price_str = price_str
370
+ if price_rebate_str:
371
+ try:
372
+ normal_price = float(price_str)
373
+ rebate_price = float(price_rebate_str)
374
+ # Kampanya fiyatı normal fiyattan farklı ve düşükse kullan
375
+ if rebate_price < normal_price:
376
+ final_price_str = price_rebate_str
377
+ except (ValueError, TypeError):
378
+ final_price_str = price_str
379
+
380
+ # EFT fiyatı
381
+ price_eft_element = item.find('priceEft')
382
+ price_eft_str = price_eft_element.text if price_eft_element is not None and price_eft_element.text else ""
383
+
384
+ # Ürün linki
385
+ link_element = item.find('productLink')
386
+ product_link = link_element.text if link_element is not None and link_element.text else ""
387
+
388
+ # Fiyat formatting (kampanya fiyatı veya normal fiyat)
389
+ try:
390
+ price_float = float(final_price_str)
391
+ if price_float > 200000:
392
+ price = str(round(price_float / 5000) * 5000)
393
+ elif price_float > 30000:
394
+ price = str(round(price_float / 1000) * 1000)
395
+ elif price_float > 10000:
396
+ price = str(round(price_float / 100) * 100)
397
+ else:
398
+ price = str(round(price_float / 10) * 10)
399
+ except (ValueError, TypeError):
400
+ price = final_price_str
401
+
402
+ # EFT fiyat formatting
403
+ if price_eft_str:
404
+ try:
405
+ price_eft_float = float(price_eft_str)
406
+ if price_eft_float > 200000:
407
+ price_eft = str(round(price_eft_float / 5000) * 5000)
408
+ elif price_eft_float > 30000:
409
+ price_eft = str(round(price_eft_float / 1000) * 1000)
410
+ elif price_eft_float > 10000:
411
+ price_eft = str(round(price_eft_float / 100) * 100)
412
+ else:
413
+ price_eft = str(round(price_eft_float / 10) * 10)
414
+ except (ValueError, TypeError):
415
+ price_eft = price_eft_str
416
+ else:
417
+ try:
418
+ price_eft_float = float(price_str)
419
+ price_eft = str(round(price_eft_float * 0.975 / 10) * 10)
420
+ except:
421
+ price_eft = ""
422
+
423
+ # Ürün bilgilerini tuple olarak oluştur
424
+ item_info = (stock_amount, price, product_link, price_eft, str(stock_number))
425
+ products.append((name, item_info, full_name))
426
+
427
+ # Summary disabled for production
428
+
429
+ # Initialize improved WhatsApp chatbot for BF space
430
+ global improved_whatsapp_bot
431
+ improved_whatsapp_bot = None
432
+ if USE_IMPROVED_SEARCH:
433
+ try:
434
+ improved_whatsapp_bot = WhatsAppImprovedChatbot(products)
435
+ # print("✅ BF Space: Improved WhatsApp product search initialized successfully")
436
+ except Exception as e:
437
+ logger.error(f"BF Space: Failed to initialize improved WhatsApp search: {e}")
438
+ USE_IMPROVED_SEARCH = False
439
+ improved_whatsapp_bot = None
440
+
441
+ # Enhanced features kaldırıldı - GPT-4 doğal dil anlama kullanacak
442
+ # print("✅ Basit sistem aktif - GPT-4 doğal dil anlama")
443
+
444
+ # Marlin debug reports disabled for production
445
+ if marlin_count == 0:
446
+ pass # No Marlin products found
447
+ else:
448
+ # Marlin stok raporu
449
+ marlin_products = [p for p in products if 'marlin' in p[2].lower()]
450
+ marlin_in_stock = [p for p in marlin_products if p[1][0] == "stokta"]
451
+ marlin_out_of_stock = [p for p in marlin_products if p[1][0] == "stokta değil"]
452
+
453
+ # Product lists disabled for production
454
+ pass
455
+
456
+ except Exception as e:
457
+ logger.error(f"Ürün yükleme hatası: {e}")
458
+ import traceback
459
+ traceback.print_exc()
460
+ products = []
461
+
462
+ # ===============================
463
+ # STOK API ENTEGRASYONU
464
+ # ===============================
465
+
466
+ STOCK_API_BASE = "https://video.trek-turkey.com/bizimhesap-proxy.php"
467
+
468
+ # Stock cache (5 dakikalık cache)
469
+ stock_cache = {}
470
+ CACHE_DURATION = 300 # 5 dakika (saniye cinsinden)
471
+
472
+ def normalize_turkish(text):
473
+ """Türkçe karakterleri normalize et"""
474
+ if not text:
475
+ return ""
476
+ replacements = {
477
+ 'ı': 'i', 'İ': 'i', 'ş': 's', 'Ş': 's',
478
+ 'ğ': 'g', 'Ğ': 'g', 'ü': 'u', 'Ü': 'u',
479
+ 'ö': 'o', 'Ö': 'o', 'ç': 'c', 'Ç': 'c'
480
+ }
481
+ text = text.lower()
482
+ for tr_char, eng_char in replacements.items():
483
+ text = text.replace(tr_char, eng_char)
484
+ return text
485
+
486
+ def fetch_warehouse_inventory(warehouse, product_name, search_terms):
487
+ """Tek bir mağazanın stok bilgisini al"""
488
+ try:
489
+ warehouse_id = warehouse['id']
490
+ warehouse_name = warehouse['title']
491
+
492
+ # DSW'yi ayrı tut (gelecek stok için)
493
+ is_dsw = 'DSW' in warehouse_name or 'ÖN SİPARİŞ' in warehouse_name.upper()
494
+
495
+ # Mağaza stoklarını al
496
+ inventory_url = f"{STOCK_API_BASE}?action=inventory&warehouse={warehouse_id}&endpoint=inventory/{warehouse_id}"
497
+ inventory_response = requests.get(inventory_url, timeout=3, verify=False)
498
+
499
+ if inventory_response.status_code != 200:
500
+ return None
501
+
502
+ inventory_data = inventory_response.json()
503
+
504
+ # API yanıtını kontrol et
505
+ if 'data' not in inventory_data or 'inventory' not in inventory_data['data']:
506
+ return None
507
+
508
+ products = inventory_data['data']['inventory']
509
+
510
+ # Beden terimleri kontrolü
511
+ size_terms = ['xs', 's', 'm', 'ml', 'l', 'xl', 'xxl', '2xl', '3xl', 'small', 'medium', 'large']
512
+ size_numbers = ['44', '46', '48', '50', '52', '54', '56', '58', '60']
513
+
514
+ # Arama terimlerinde beden var mı kontrol et
515
+ has_size_query = False
516
+ size_query = None
517
+ for term in search_terms:
518
+ if term in size_terms or term in size_numbers:
519
+ has_size_query = True
520
+ size_query = term
521
+ break
522
+
523
+ # Eğer sadece beden sorgusu varsa (ör: "m", "xl")
524
+ is_only_size_query = len(search_terms) == 1 and has_size_query
525
+
526
+ # Ürünü ara
527
+ warehouse_variants = []
528
+ dsw_stock_count = 0
529
+
530
+ for product in products:
531
+ product_title = normalize_turkish(product.get('title', '')).lower()
532
+ original_title = product.get('title', '')
533
+
534
+ # Eğer sadece beden sorgusu ise
535
+ if is_only_size_query:
536
+ # Beden terimini ürün başlığında ara (parantez içinde veya dışında)
537
+ 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}'):
538
+ qty = int(product.get('qty', 0))
539
+ stock = int(product.get('stock', 0))
540
+ actual_stock = max(qty, stock)
541
+
542
+ if actual_stock > 0:
543
+ if is_dsw:
544
+ dsw_stock_count += actual_stock
545
+ continue
546
+ warehouse_variants.append(f"{original_title}: ✓ Stokta")
547
+ else:
548
+ # Normal ürün araması - tüm terimler eşleşmeli
549
+ # Ama beden terimi varsa özel kontrol yap
550
+ if has_size_query:
551
+ # Beden hariç diğer terimleri kontrol et
552
+ non_size_terms = [t for t in search_terms if t != size_query]
553
+ product_matches = all(term in product_title for term in non_size_terms)
554
+
555
+ # Beden kontrolü - daha esnek
556
+ 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}')
557
+
558
+ if product_matches and size_matches:
559
+ qty = int(product.get('qty', 0))
560
+ stock = int(product.get('stock', 0))
561
+ actual_stock = max(qty, stock)
562
+
563
+ if actual_stock > 0:
564
+ if is_dsw:
565
+ dsw_stock_count += actual_stock
566
+ continue
567
+
568
+ # Varyant bilgisini göster
569
+ variant_info = original_title
570
+ possible_names = [
571
+ product_name.upper(),
572
+ product_name.lower(),
573
+ product_name.title(),
574
+ product_name.upper().replace('I', 'İ'),
575
+ product_name.upper().replace('İ', 'I'),
576
+ ]
577
+
578
+ if 'fx sport' in product_name.lower():
579
+ possible_names.extend(['FX Sport AL 3', 'FX SPORT AL 3', 'Fx Sport Al 3'])
580
+
581
+ for possible_name in possible_names:
582
+ variant_info = variant_info.replace(possible_name, '').strip()
583
+
584
+ variant_info = ' '.join(variant_info.split())
585
+
586
+ if variant_info and variant_info != original_title:
587
+ warehouse_variants.append(f"{variant_info}: ✓ Stokta")
588
+ else:
589
+ warehouse_variants.append(f"{original_title}: ✓ Stokta")
590
+ else:
591
+ # Beden sorgusu yoksa normal kontrol
592
+ if all(term in product_title for term in search_terms):
593
+ qty = int(product.get('qty', 0))
594
+ stock = int(product.get('stock', 0))
595
+ actual_stock = max(qty, stock)
596
+
597
+ if actual_stock > 0:
598
+ if is_dsw:
599
+ dsw_stock_count += actual_stock
600
+ continue
601
+
602
+ variant_info = original_title
603
+ possible_names = [
604
+ product_name.upper(),
605
+ product_name.lower(),
606
+ product_name.title(),
607
+ product_name.upper().replace('I', 'İ'),
608
+ product_name.upper().replace('İ', 'I'),
609
+ ]
610
+
611
+ if 'fx sport' in product_name.lower():
612
+ possible_names.extend(['FX Sport AL 3', 'FX SPORT AL 3', 'Fx Sport Al 3'])
613
+
614
+ for possible_name in possible_names:
615
+ variant_info = variant_info.replace(possible_name, '').strip()
616
+
617
+ variant_info = ' '.join(variant_info.split())
618
+
619
+ if variant_info and variant_info != original_title:
620
+ warehouse_variants.append(f"{variant_info}: ✓ Stokta")
621
+ else:
622
+ warehouse_variants.append(f"{original_title}: ✓ Stokta")
623
+
624
+ # Sonuç döndür
625
+ if warehouse_variants and not is_dsw:
626
+ return {'warehouse': warehouse_name, 'variants': warehouse_variants, 'is_dsw': False}
627
+ elif dsw_stock_count > 0:
628
+ return {'dsw_stock': dsw_stock_count, 'is_dsw': True}
629
+
630
+ return None
631
+
632
+ except Exception:
633
+ return None
634
+
635
+ def get_realtime_stock_parallel(product_name):
636
+ """API'den gerçek zamanlı stok bilgisini çek - Paralel versiyon with cache"""
637
+ try:
638
+ # Cache kontrolü
639
+ cache_key = normalize_turkish(product_name).lower()
640
+ current_time = time.time()
641
+
642
+ if cache_key in stock_cache:
643
+ cached_data, cached_time = stock_cache[cache_key]
644
+ # Cache hala geçerli mi?
645
+ if current_time - cached_time < CACHE_DURATION:
646
+ logger.info(f"Cache'den döndürülüyor: {product_name}")
647
+ return cached_data
648
+
649
+ # Önce mağaza listesini al
650
+ warehouses_url = f"{STOCK_API_BASE}?action=warehouses&endpoint=warehouses"
651
+ warehouses_response = requests.get(warehouses_url, timeout=3, verify=False)
652
+
653
+ if warehouses_response.status_code != 200:
654
+ logger.error(f"Mağaza listesi alınamadı: {warehouses_response.status_code}")
655
+ return None
656
+
657
+ warehouses_data = warehouses_response.json()
658
+
659
+ # API yanıtını kontrol et
660
+ if 'data' not in warehouses_data or 'warehouses' not in warehouses_data['data']:
661
+ logger.error("Mağaza verisi bulunamadı")
662
+ return None
663
+
664
+ warehouses = warehouses_data['data']['warehouses']
665
+
666
+ # Ürün adını normalize et
667
+ search_terms = normalize_turkish(product_name).lower().split()
668
+ logger.info(f"Aranan ürün: {product_name} -> {search_terms}")
669
+
670
+ stock_info = {}
671
+ total_dsw_stock = 0
672
+ total_stock = 0
673
+
674
+ # Paralel olarak tüm mağazaları sorgula
675
+ with ThreadPoolExecutor(max_workers=10) as executor:
676
+ # Tüm mağazalar için görev oluştur
677
+ futures = {
678
+ executor.submit(fetch_warehouse_inventory, warehouse, product_name, search_terms): warehouse
679
+ for warehouse in warehouses
680
+ }
681
+
682
+ # Sonuçları topla
683
+ for future in as_completed(futures):
684
+ result = future.result()
685
+ if result:
686
+ if result.get('is_dsw'):
687
+ total_dsw_stock += result.get('dsw_stock', 0)
688
+ else:
689
+ warehouse_name = result['warehouse']
690
+ stock_info[warehouse_name] = result['variants']
691
+ total_stock += 1 # En az bir mağazada var
692
+
693
+ # Sonucu oluştur
694
+ if not stock_info:
695
+ # Mağazada yok ama DSW'de varsa
696
+ if total_dsw_stock > 0:
697
+ result = f"{product_name}: Şu anda mağazalarda stokta yok, ancak yakında gelecek. Ön sipariş verebilirsiniz."
698
+ else:
699
+ result = f"{product_name}: Şu anda hiçbir mağazada stokta bulunmuyor."
700
+ else:
701
+ # Minimal prompt oluştur - varyant detaylarıyla
702
+ prompt_lines = [f"{product_name} stok durumu:"]
703
+ for warehouse, variants in stock_info.items():
704
+ if isinstance(variants, list):
705
+ prompt_lines.append(f"- {warehouse}:")
706
+ for variant in variants:
707
+ prompt_lines.append(f" • {variant}")
708
+ else:
709
+ prompt_lines.append(f"- {warehouse}: {variants}")
710
+
711
+ # Güvenlik: Toplam adet bilgisi gösterme
712
+ if total_stock > 0:
713
+ prompt_lines.append(f"✓ Ürün stokta mevcut")
714
+
715
+ result = "\n".join(prompt_lines)
716
+
717
+ # Sonucu cache'e kaydet
718
+ stock_cache[cache_key] = (result, current_time)
719
+
720
+ return result
721
+
722
+ except Exception as e:
723
+ logger.error(f"API hatası: {e}")
724
+ return None
725
+
726
+ def is_stock_query(message):
727
+ """Mesajın stok sorgusu olup olmadığını kontrol et"""
728
+ stock_keywords = ['stok', 'stock', 'var mı', 'mevcut mu', 'kaç adet',
729
+ 'kaç tane', 'bulunuyor mu', 'hangi mağaza',
730
+ 'nerede var', 'beden', 'numara']
731
+ message_lower = message.lower()
732
+ return any(keyword in message_lower for keyword in stock_keywords)
733
+
734
+ # Sistem mesajları - Modüler prompts'tan yükle
735
+ def get_system_messages():
736
+ return get_prompt_content_only() # prompts.py'dan yükle
737
+
738
+ # ===============================
739
+ # SOHBET HAFIZASI SİSTEMİ
740
+ # ===============================
741
+
742
+ # Sohbet hafızası için basit bir dictionary
743
+ conversation_memory = {}
744
+
745
+ def get_conversation_context(phone_number):
746
+ """Kullanıcının sohbet geçmişini getir"""
747
+ if phone_number not in conversation_memory:
748
+ conversation_memory[phone_number] = {
749
+ "messages": [],
750
+ "current_category": None,
751
+ "last_activity": None
752
+ }
753
+ return conversation_memory[phone_number]
754
+
755
+ def add_to_conversation(phone_number, user_message, ai_response):
756
+ """Sohbet geçmişine ekle"""
757
+ import datetime
758
+
759
+ context = get_conversation_context(phone_number)
760
+ context["last_activity"] = datetime.datetime.now()
761
+
762
+ context["messages"].append({
763
+ "user": user_message,
764
+ "ai": ai_response,
765
+ "timestamp": datetime.datetime.now()
766
+ })
767
+
768
+ # Sadece son 10 mesajı tut
769
+ if len(context["messages"]) > 10:
770
+ context["messages"] = context["messages"][-10:]
771
+
772
+ detect_category(phone_number, user_message, ai_response)
773
+
774
+ def detect_category(phone_number, user_message, ai_response):
775
+ """Konuşulan kategoriyi tespit et"""
776
+ context = get_conversation_context(phone_number)
777
+
778
+ categories = {
779
+ "marlin": ["marlin", "marlin+"],
780
+ "madone": ["madone"],
781
+ "emonda": ["emonda", "émonda"],
782
+ "domane": ["domane"],
783
+ "checkpoint": ["checkpoint"],
784
+ "fuel": ["fuel", "fuel ex", "fuel exe"],
785
+ "procaliber": ["procaliber"],
786
+ "supercaliber": ["supercaliber"],
787
+ "fx": ["fx"],
788
+ "ds": ["ds", "dual sport"],
789
+ "powerfly": ["powerfly"],
790
+ "rail": ["rail"],
791
+ "verve": ["verve"],
792
+ "townie": ["townie"]
793
+ }
794
+
795
+ user_lower = user_message.lower()
796
+ for category, keywords in categories.items():
797
+ for keyword in keywords:
798
+ if keyword in user_lower:
799
+ context["current_category"] = category
800
+ return category
801
+
802
+ return context.get("current_category")
803
+
804
+ def build_context_messages(phone_number, current_message):
805
+ """Sohbet geçmişi ile sistem mesajlarını oluştur"""
806
+ context = get_conversation_context(phone_number)
807
+ system_messages = get_system_messages()
808
+
809
+ # Mevcut kategori varsa, sistem mesajına ekle
810
+ if context.get("current_category"):
811
+ 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."
812
+ system_messages.append({"role": "system", "content": category_msg})
813
+
814
+ # Son 3 mesaj alışverişini ekle
815
+ recent_messages = context["messages"][-3:] if context["messages"] else []
816
+
817
+ all_messages = system_messages.copy()
818
+
819
+ # Geçmiş mesajları ekle
820
+ for msg in recent_messages:
821
+ all_messages.append({"role": "user", "content": msg["user"]})
822
+ all_messages.append({"role": "assistant", "content": msg["ai"]})
823
+
824
+ # Mevcut mesajı ekle
825
+ all_messages.append({"role": "user", "content": current_message})
826
+
827
+ return all_messages
828
+
829
+ def process_whatsapp_message_with_media(user_message, phone_number, media_urls, media_types):
830
+ """Medya içeriği olan WhatsApp mesajı işleme - GPT-5 Vision ile"""
831
+ try:
832
+ logger.info(f"🖼️ Medya analizi başlıyor: {len(media_urls)} medya")
833
+
834
+ # Pasif profil analizi
835
+ profile_analysis = analyze_user_message(phone_number, user_message)
836
+ logger.info(f"📊 Profil analizi: {phone_number} -> {profile_analysis}")
837
+
838
+ # Sohbet geçmişi ile sistem mesajlarını oluştur
839
+ messages = build_context_messages(phone_number, user_message if user_message else "Gönderilen görseli analiz et")
840
+
841
+ # GPT-5 Vision için mesaj hazırla
842
+ vision_message = {
843
+ "role": "user",
844
+ "content": []
845
+ }
846
+
847
+ # Metin mesajı varsa ekle
848
+ if user_message and user_message.strip():
849
+ vision_message["content"].append({
850
+ "type": "text",
851
+ "text": user_message
852
+ })
853
+ else:
854
+ vision_message["content"].append({
855
+ "type": "text",
856
+ "text": "Bu görselde ne var? Detaylı açıkla."
857
+ })
858
+
859
+ # Medya URL'lerini ekle (Twilio medya URL'leri için proxy kullan)
860
+ for i, media_url in enumerate(media_urls):
861
+ media_type = media_types[i] if i < len(media_types) else "image/jpeg"
862
+
863
+ # Sadece görsel medyaları işle
864
+ if media_type and media_type.startswith('image/'):
865
+ # Twilio medya URL'sini proxy üzerinden çevir
866
+ if 'api.twilio.com' in media_url:
867
+ # URL'den message SID ve media SID'yi çıkar
868
+ import re
869
+ match = re.search(r'/Messages/([^/]+)/Media/([^/]+)', media_url)
870
+ if match:
871
+ message_sid = match.group(1)
872
+ media_sid = match.group(2)
873
+ # Proxy URL'sini oluştur
874
+ proxy_url = f"https://video.trek-turkey.com/twilio-media-proxy.php?action=media&message={message_sid}&media={media_sid}"
875
+ logger.info(f"🔄 Proxy URL: {proxy_url}")
876
+
877
+ vision_message["content"].append({
878
+ "type": "image_url",
879
+ "image_url": {
880
+ "url": proxy_url
881
+ }
882
+ })
883
+ else:
884
+ # Diğer URL'leri doğrudan kullan
885
+ vision_message["content"].append({
886
+ "type": "image_url",
887
+ "image_url": {
888
+ "url": media_url
889
+ }
890
+ })
891
+
892
+ # Son user mesajını vision mesajıyla değiştir
893
+ messages = [msg for msg in messages if msg.get("role") != "user" or msg != messages[-1]]
894
+ messages.append(vision_message)
895
+
896
+ # Sistem mesajına bisiklet tanıma talimatı ekle
897
+ messages.insert(0, {
898
+ "role": "system",
899
+ "content": "Gönderilen görsellerde bisiklet veya bisiklet parçaları varsa, bunları detaylıca tanımla. Marka, model, renk, özellikler gibi detayları belirt. Eğer Trek bisiklet ise modeli tahmin etmeye çalış. Stok durumu sorulursa, görseldeki bisikletin özelliklerini belirterek stok kontrolü yapılması gerektiğini söyle."
900
+ })
901
+
902
+ if not OPENAI_API_KEY:
903
+ return "OpenAI API anahtarı eksik. Lütfen environment variables'ları kontrol edin."
904
+
905
+ logger.info(f"📤 GPT-5 Vision'a gönderiliyor: {len(messages)} mesaj")
906
+
907
+ payload = {
908
+ "model": "gpt-5-chat-latest",
909
+ "messages": messages,
910
+ "temperature": 0, # Deterministik cevaplar için
911
+ "max_tokens": 800,
912
+ "stream": False,
913
+ "top_p": 0.1, # Daha tutarlı cevaplar için düşük değer
914
+ "frequency_penalty": 0.1, # Tekrarları azaltmak için
915
+ "presence_penalty": 0 # Yeni konulara açık olması için
916
+ }
917
+
918
+ headers = {
919
+ "Content-Type": "application/json",
920
+ "Authorization": f"Bearer {OPENAI_API_KEY}"
921
+ }
922
+
923
+ response = requests.post(API_URL, headers=headers, json=payload)
924
+ if response.status_code == 200:
925
+ result = response.json()
926
+ ai_response = result['choices'][0]['message']['content']
927
+
928
+ # WhatsApp için formatla
929
+ formatted_response = extract_product_info_whatsapp(ai_response)
930
+
931
+ # Sohbet geçmişine ekle
932
+ add_to_conversation(phone_number, f"[Görsel gönderildi] {user_message if user_message else ''}", formatted_response)
933
+
934
+ return formatted_response
935
+ else:
936
+ logger.error(f"OpenAI API Error: {response.status_code} - {response.text}")
937
+ return f"Görsel analizi başarısız oldu. Lütfen tekrar deneyin."
938
+
939
+ except Exception as e:
940
+ logger.error(f"❌ Medya işleme hatası: {e}")
941
+ import traceback
942
+ traceback.print_exc()
943
+ return "Görsel işlenirken bir hata oluştu. Lütfen tekrar deneyin."
944
+
945
+ def process_whatsapp_message_with_memory(user_message, phone_number):
946
+ """Hafızalı WhatsApp mesaj işleme"""
947
+ try:
948
+ # 🔔 Yeni Mağaza Bildirim Sistemi - Mehmet Bey'e otomatik bildirim
949
+ if USE_STORE_NOTIFICATION:
950
+ # Önce basit keyword kontrolü yap
951
+ should_notify_mehmet, notification_reason, urgency = should_notify_mehmet_bey(user_message)
952
+
953
+ # Eğer keyword'le yakalanmadıysa ve Intent Analyzer varsa, onu da kontrol et
954
+ if not should_notify_mehmet and USE_INTENT_ANALYZER:
955
+ context = get_conversation_context(phone_number)
956
+ intent_analysis = analyze_customer_intent(user_message, context)
957
+ should_notify_mehmet, notification_reason, urgency = should_notify_mehmet_bey(user_message, intent_analysis)
958
+ else:
959
+ intent_analysis = None
960
+
961
+ if should_notify_mehmet:
962
+ # Ürün bilgisini belirle
963
+ if intent_analysis:
964
+ product = intent_analysis.get("product") or "Belirtilmemiş"
965
+ else:
966
+ # Basit keyword'den ürün çıkar
967
+ context = get_conversation_context(phone_number)
968
+ product = context.get("current_category") or "Ürün belirtilmemiş"
969
+
970
+ # Bildirim tipini belirle
971
+ if "rezervasyon" in notification_reason.lower() or urgency == "high":
972
+ action = "reserve"
973
+ elif "mağaza" in notification_reason.lower() or "lokasyon" in notification_reason.lower():
974
+ action = "info"
975
+ elif "fiyat" in notification_reason.lower() or "ödeme" in notification_reason.lower():
976
+ action = "price"
977
+ else:
978
+ action = "info"
979
+
980
+ # Detaylı bilgi mesajı
981
+ additional_info = f"{notification_reason}\n\nMüşteri Mesajı: '{user_message}'"
982
+ if urgency == "high":
983
+ additional_info = "⚠️ YÜKSEK ÖNCELİK ⚠️\n" + additional_info
984
+
985
+ # Bildirim gönder
986
+ result = send_store_notification(
987
+ customer_phone=phone_number,
988
+ customer_name=None,
989
+ product_name=product,
990
+ action=action,
991
+ store_name=None,
992
+ additional_info=additional_info
993
+ )
994
+
995
+ if result:
996
+ logger.info(f"✅ Mehmet Bey'e bildirim gönderildi!")
997
+ logger.info(f" 📍 Sebep: {notification_reason}")
998
+ logger.info(f" ⚡ Öncelik: {urgency}")
999
+ logger.info(f" 📦 Ürün: {product}")
1000
+
1001
+ # TAKIP SISTEMINI KONTROL ET
1002
+ if USE_FOLLOW_UP and follow_up_manager:
1003
+ follow_up_analysis = analyze_message_for_follow_up(user_message)
1004
+ if follow_up_analysis and follow_up_analysis["needs_follow_up"]:
1005
+ # Takip oluştur
1006
+ follow_up = follow_up_manager.create_follow_up(
1007
+ customer_phone=phone_number,
1008
+ product_name=product,
1009
+ follow_up_type=follow_up_analysis["follow_up_type"],
1010
+ original_message=user_message,
1011
+ follow_up_hours=follow_up_analysis["follow_up_hours"],
1012
+ notes=follow_up_analysis["reason"]
1013
+ )
1014
+ logger.info(f"📌 Takip oluşturuldu: {follow_up_analysis['reason']}")
1015
+ logger.info(f" ⏰ {follow_up_analysis['follow_up_hours']} saat sonra hatırlatılacak")
1016
+ else:
1017
+ logger.error("❌ Mehmet Bey'e bildirim gönderilemedi")
1018
+
1019
+
1020
+ # 🧠 Pasif profil analizi - kullanıcı mesajını analiz et
1021
+ profile_analysis = analyze_user_message(phone_number, user_message)
1022
+ logger.info(f"📊 Profil analizi: {phone_number} -> {profile_analysis}")
1023
+
1024
+ # 🎯 Kişiselleştirilmiş öneriler kontrolü
1025
+ if any(keyword in user_message.lower() for keyword in ["öneri", "öner", "tavsiye", "ne önerirsin"]):
1026
+ personalized = get_personalized_recommendations(phone_number, products)
1027
+ if personalized.get("personalized") and personalized.get("recommendations"):
1028
+ # Kullanıcı profiline göre özelleştirilmiş cevap hazırla
1029
+ profile_summary = get_user_profile_summary(phone_number)
1030
+ custom_response = create_personalized_response(personalized, profile_summary)
1031
+ return extract_product_info_whatsapp(custom_response)
1032
+
1033
+ # Enhanced features kaldırıldı - GPT-4 doğal dil anlama kullanacak
1034
+
1035
+ # Sohbet geçmişi ile sistem mesajlarını oluştur
1036
+ messages = build_context_messages(phone_number, user_message)
1037
+
1038
+ # 🎯 Profil bilgilerini sistem mesajlarına ekle
1039
+ profile_summary = get_user_profile_summary(phone_number)
1040
+ if profile_summary.get("exists") and profile_summary.get("confidence", 0) > 0.3:
1041
+ profile_context = create_profile_context_message(profile_summary)
1042
+ messages.append({"role": "system", "content": profile_context})
1043
+
1044
+ # 🔍 BF Space: Use improved product search if available
1045
+ product_found_improved = False
1046
+ if USE_IMPROVED_SEARCH and improved_whatsapp_bot:
1047
+ try:
1048
+ product_result = improved_whatsapp_bot.process_message(user_message)
1049
+ if product_result['is_product_query'] and product_result['response']:
1050
+ # Check if user is asking about specific warehouse/store location
1051
+ if any(keyword in user_message.lower() for keyword in ['mağaza', 'mağazada', 'nerede', 'hangi mağaza', 'şube']):
1052
+ # First, always search for products using improved search
1053
+ # This will find products even with partial/typo names
1054
+ warehouse_info_parts = []
1055
+
1056
+ # Use the response text from improved search to extract product names
1057
+ if product_result['response']:
1058
+ # Extract product names from the response
1059
+ import re
1060
+ # Look for product names in bold (between * markers)
1061
+ product_names = re.findall(r'\*([^*]+)\*', product_result['response'])
1062
+
1063
+ if product_names:
1064
+ for product_name in product_names[:3]: # Max 3 products
1065
+ # Clean up the product name
1066
+ product_name = product_name.strip()
1067
+
1068
+ # Remove numbering like "1." "2." from the beginning
1069
+ import re
1070
+ product_name = re.sub(r'^\d+\.\s*', '', product_name)
1071
+
1072
+ # Skip status indicators
1073
+ if product_name in ['Stokta mevcut', 'Stokta yok', 'Fiyat:', 'Kampanya:', 'İndirim:', 'Birden fazla ürün buldum:']:
1074
+ continue
1075
+
1076
+ warehouse_stock = get_warehouse_stock(product_name)
1077
+
1078
+ if warehouse_stock and warehouse_stock != ['Ürün bulunamadı'] and warehouse_stock != ['Hiçbir mağazada stokta bulunmuyor']:
1079
+ warehouse_info_parts.append(f"{product_name} mağaza stogu:")
1080
+ warehouse_info_parts.extend(warehouse_stock)
1081
+ warehouse_info_parts.append("")
1082
+ break # Found warehouse info, stop searching
1083
+
1084
+ # If still no warehouse info, use products_found as backup
1085
+ if not warehouse_info_parts and product_result['products_found']:
1086
+ for product in product_result['products_found'][:2]:
1087
+ product_name = product[2] # Full product name
1088
+ warehouse_stock = get_warehouse_stock(product_name)
1089
+
1090
+ if warehouse_stock and warehouse_stock != ['Ürün bulunamadı'] and warehouse_stock != ['Hiçbir mağazada stokta bulunmuyor']:
1091
+ warehouse_info_parts.append(f"{product_name} mağaza stogu:")
1092
+ warehouse_info_parts.extend(warehouse_stock)
1093
+ warehouse_info_parts.append("")
1094
+ break
1095
+
1096
+ if warehouse_info_parts:
1097
+ warehouse_response = "\n".join(warehouse_info_parts)
1098
+ messages.append({
1099
+ "role": "system",
1100
+ "content": f"MAĞAZA STOK BİLGİSİ (BF Space):\n{warehouse_response}\n\nSADECE bu bilgileri kullanarak kullanıcıya yardımcı ol."
1101
+ })
1102
+ product_found_improved = True
1103
+ logger.info("✅ BF Space: Warehouse stock info used")
1104
+
1105
+ if not product_found_improved:
1106
+ # Use improved search response directly
1107
+ messages.append({
1108
+ "role": "system",
1109
+ "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."
1110
+ })
1111
+ product_found_improved = True
1112
+ logger.info("✅ BF Space: Improved product search used")
1113
+ except Exception as e:
1114
+ logger.error(f"❌ BF Space: Improved search error: {e}")
1115
+
1116
+ # Fallback to warehouse search if improved search didn't work
1117
+ if not product_found_improved:
1118
+ # Check if message seems to be asking about products
1119
+ product_keywords = ['fiyat', 'kaç', 'stok', 'var mı', 'mevcut', 'bisiklet', 'bike',
1120
+ 'trek', 'model', 'beden', 'renk', 'mağaza', 'nerede', 'hangi']
1121
+
1122
+ # Common non-product responses
1123
+ non_product_responses = ['süper', 'harika', 'güzel', 'teşekkür', 'tamam', 'olur',
1124
+ 'evet', 'hayır', 'peki', 'anladım', 'tamamdır']
1125
+
1126
+ is_product_query = False
1127
+ lower_message = user_message.lower()
1128
+
1129
+ # Check if it's likely a product query
1130
+ if any(keyword in lower_message for keyword in product_keywords):
1131
+ is_product_query = True
1132
+ # Check if it's NOT a simple response
1133
+ elif lower_message not in non_product_responses and len(lower_message.split()) > 1:
1134
+ # Multi-word queries might be product searches
1135
+ is_product_query = True
1136
+ # Single short words are usually not products
1137
+ elif len(lower_message.split()) == 1 and len(lower_message) < 6:
1138
+ is_product_query = False
1139
+
1140
+ if is_product_query:
1141
+ # Use GPT-5 warehouse search for product queries
1142
+ logger.info("📦 Using GPT-5 warehouse search")
1143
+ warehouse_result = get_warehouse_stock(user_message)
1144
+ if warehouse_result and warehouse_result != ['Ürün bulunamadı']:
1145
+ warehouse_response = "\n".join(warehouse_result)
1146
+ messages.append({
1147
+ "role": "system",
1148
+ "content": f"MAĞAZA STOK BİLGİSİ:\n{warehouse_response}\n\nBu bilgileri kullanarak kullanıcıya yardımcı ol. Fiyat ve stok bilgilerini AYNEN kullan."
1149
+ })
1150
+ logger.info(f"✅ Warehouse stock info added: {warehouse_response[:200]}...")
1151
+ else:
1152
+ logger.info(f"🚫 Skipping product search for: '{user_message}'")
1153
+
1154
+ if not OPENAI_API_KEY:
1155
+ return "OpenAI API anahtarı eksik. Lütfen environment variables'ları kontrol edin."
1156
+
1157
+ # Debug: Log what we're sending to GPT
1158
+ logger.info(f"📤 Sending to GPT-5: {len(messages)} messages")
1159
+ for i, msg in enumerate(messages):
1160
+ if msg.get('role') == 'system':
1161
+ content_preview = msg.get('content', '')[:500]
1162
+ if 'Fiyat:' in content_preview or 'TL' in content_preview:
1163
+ logger.info(f"💰 System message with price info: {content_preview}")
1164
+
1165
+ payload = {
1166
+ "model": "gpt-5-chat-latest",
1167
+ "messages": messages,
1168
+ "temperature": 0, # Deterministik cevaplar için
1169
+ "max_tokens": 800,
1170
+ "stream": False,
1171
+ "top_p": 0.1, # Daha tutarlı cevaplar için düşük değer
1172
+ "frequency_penalty": 0.1, # Tekrarları azaltmak için
1173
+ "presence_penalty": 0 # Yeni konulara açık olması için
1174
+ }
1175
+
1176
+ headers = {
1177
+ "Content-Type": "application/json",
1178
+ "Authorization": f"Bearer {OPENAI_API_KEY}"
1179
+ }
1180
+
1181
+ response = requests.post(API_URL, headers=headers, json=payload)
1182
+ if response.status_code == 200:
1183
+ result = response.json()
1184
+ ai_response = result['choices'][0]['message']['content']
1185
+
1186
+ # WhatsApp için resim URL'lerini formatla
1187
+ formatted_response = extract_product_info_whatsapp(ai_response)
1188
+
1189
+ # Sohbet geçmişine ekle
1190
+ add_to_conversation(phone_number, user_message, formatted_response)
1191
+
1192
+ return formatted_response
1193
+ else:
1194
+ print(f"OpenAI API Error: {response.status_code} - {response.text}")
1195
+ return f"API hatası: {response.status_code}. Lütfen daha sonra tekrar deneyin."
1196
+
1197
+ except Exception as e:
1198
+ print(f"❌ WhatsApp mesaj işleme hatası: {e}")
1199
+ import traceback
1200
+ traceback.print_exc()
1201
+ logger.error(f"Detailed error: {str(e)}")
1202
+ logger.error(f"Error type: {type(e).__name__}")
1203
+ return "Teknik bir sorun oluştu. Lütfen daha sonra tekrar deneyin."
1204
+
1205
+ def create_profile_context_message(profile_summary):
1206
+ """Profil bilgilerini sistem mesajına çevir"""
1207
+ context_parts = []
1208
+
1209
+ preferences = profile_summary.get("preferences", {})
1210
+ behavior = profile_summary.get("behavior", {})
1211
+
1212
+ # Bütçe bilgisi
1213
+ if preferences.get("budget_min") and preferences.get("budget_max"):
1214
+ budget_min = preferences["budget_min"]
1215
+ budget_max = preferences["budget_max"]
1216
+ context_parts.append(f"Kullanıcının bütçesi: {budget_min:,}-{budget_max:,} TL")
1217
+
1218
+ # Kategori tercihleri
1219
+ if preferences.get("categories"):
1220
+ categories = ", ".join(preferences["categories"])
1221
+ context_parts.append(f"İlgilendiği kategoriler: {categories}")
1222
+
1223
+ # Kullanım amacı
1224
+ if preferences.get("usage_purpose"):
1225
+ purposes = ", ".join(preferences["usage_purpose"])
1226
+ context_parts.append(f"Kullanım amacı: {purposes}")
1227
+
1228
+ # Davranış kalıpları
1229
+ if behavior.get("price_sensitive"):
1230
+ context_parts.append("Fiyata duyarlı bir kullanıcı")
1231
+ if behavior.get("tech_interested"):
1232
+ context_parts.append("Teknik detaylarla ilgilenen bir kullanıcı")
1233
+ if behavior.get("comparison_lover"):
1234
+ context_parts.append("Karşılaştırma yapmayı seven bir kullanıcı")
1235
+
1236
+ # Etkileşim stili
1237
+ interaction_style = profile_summary.get("interaction_style", "balanced")
1238
+ style_descriptions = {
1239
+ "analytical": "Detaylı ve analitik bilgi bekleyen",
1240
+ "budget_conscious": "Bütçe odaklı ve ekonomik seçenekleri arayan",
1241
+ "technical": "Teknik özellikler ve spesifikasyonlarla ilgilenen",
1242
+ "decisive": "Hızlı karar veren ve özet bilgi isteyen",
1243
+ "balanced": "Dengeli yaklaşım sergileyen"
1244
+ }
1245
+ context_parts.append(f"{style_descriptions.get(interaction_style, 'balanced')} bir kullanıcı")
1246
+
1247
+ if context_parts:
1248
+ return f"Kullanıcı profili: {'. '.join(context_parts)}. Bu bilgileri göz önünde bulundurarak cevap ver."
1249
+ return ""
1250
+
1251
+ def create_personalized_response(personalized_data, profile_summary):
1252
+ """Kişiselleştirilmiş öneri cevabı oluştur"""
1253
+ response_parts = []
1254
+
1255
+ # Kullanıcı stiline göre selamlama
1256
+ interaction_style = profile_summary.get("interaction_style", "balanced")
1257
+ if interaction_style == "analytical":
1258
+ response_parts.append("🔍 Profilinizi analiz ederek sizin için en uygun seçenekleri belirledim:")
1259
+ elif interaction_style == "budget_conscious":
1260
+ response_parts.append("💰 Bütçenize uygun en iyi seçenekleri hazırladım:")
1261
+ elif interaction_style == "technical":
1262
+ response_parts.append("⚙️ Teknik tercihlerinize göre önerilerim:")
1263
+ else:
1264
+ response_parts.append("🎯 Size özel seçtiklerim:")
1265
+
1266
+ # Önerileri listele
1267
+ recommendations = personalized_data.get("recommendations", [])[:3] # İlk 3 öneri
1268
+
1269
+ if recommendations:
1270
+ response_parts.append("\n")
1271
+ for i, product in enumerate(recommendations, 1):
1272
+ name, item_info, full_name = product
1273
+ price = item_info[1] if len(item_info) > 1 else "Fiyat yok"
1274
+ response_parts.append(f"**{i}. {full_name}**")
1275
+ response_parts.append(f"💰 Fiyat: {price} TL")
1276
+ response_parts.append("")
1277
+
1278
+ # Profil bazlı açıklama
1279
+ preferences = profile_summary.get("preferences", {})
1280
+ if preferences.get("categories"):
1281
+ category = preferences["categories"][0]
1282
+ response_parts.append(f"Bu öneriler {category} kategorisindeki ilginizi ve tercihlerinizi dikkate alarak hazırlandı.")
1283
+
1284
+ return "\n".join(response_parts)
1285
+
1286
+ def split_long_message(message, max_length=1600):
1287
+ """Uzun mesajları WhatsApp için uygun parçalara böler"""
1288
+ if len(message) <= max_length:
1289
+ return [message]
1290
+
1291
+ parts = []
1292
+ remaining = message
1293
+
1294
+ while len(remaining) > max_length:
1295
+ cut_point = max_length
1296
+
1297
+ # Geriye doğru git ve uygun kesme noktası ara
1298
+ for i in range(max_length, max_length - 200, -1):
1299
+ if i < len(remaining) and remaining[i] in ['.', '!', '?', '\n']:
1300
+ cut_point = i + 1
1301
+ break
1302
+ elif i < len(remaining) and remaining[i] in [' ', ',', ';']:
1303
+ cut_point = i
1304
+
1305
+ parts.append(remaining[:cut_point].strip())
1306
+ remaining = remaining[cut_point:].strip()
1307
+
1308
+ if remaining:
1309
+ parts.append(remaining)
1310
+
1311
+ return parts
1312
+
1313
+ # ===============================
1314
+ # HAFIZA SİSTEMİ SONU
1315
+ # ===============================
1316
+
1317
+ # WhatsApp mesajı işleme (eski fonksiyon - yedek için)
1318
+ def process_whatsapp_message(user_message):
1319
+ try:
1320
+ system_messages = get_system_messages()
1321
+
1322
+ # 🔍 BF Space: Use improved product search if available (backup function)
1323
+ product_found_improved = False
1324
+ if USE_IMPROVED_SEARCH and improved_whatsapp_bot:
1325
+ try:
1326
+ product_result = improved_whatsapp_bot.process_message(user_message)
1327
+ if product_result['is_product_query'] and product_result['response']:
1328
+ # Check if user is asking about specific warehouse/store location
1329
+ if any(keyword in user_message.lower() for keyword in ['mağaza', 'mağazada', 'nerede', 'hangi mağaza', 'şube']):
1330
+ # Get warehouse stock info for the found products
1331
+ if product_result['products_found']:
1332
+ warehouse_info_parts = []
1333
+ for product in product_result['products_found'][:2]: # Max 2 products
1334
+ product_name = product[2] # Full product name
1335
+ warehouse_stock = get_warehouse_stock(product_name)
1336
+ if warehouse_stock:
1337
+ warehouse_info_parts.append(f"{product_name} mağaza stogu:")
1338
+ warehouse_info_parts.extend(warehouse_stock)
1339
+ warehouse_info_parts.append("")
1340
+
1341
+ if warehouse_info_parts:
1342
+ warehouse_response = "\n".join(warehouse_info_parts)
1343
+ system_messages.append({
1344
+ "role": "system",
1345
+ "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."
1346
+ })
1347
+ product_found_improved = True
1348
+
1349
+ if not product_found_improved:
1350
+ system_messages.append({
1351
+ "role": "system",
1352
+ "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."
1353
+ })
1354
+ product_found_improved = True
1355
+ except Exception as e:
1356
+ logger.error(f"BF Space backup: Improved search error: {e}")
1357
+
1358
+ # Fallback to basic search
1359
+ if not product_found_improved:
1360
+ # Ürün bilgilerini kontrol et (basic search)
1361
+ input_words = user_message.lower().split()
1362
+ for product_info in products:
1363
+ if product_info[0] in input_words:
1364
+ if product_info[1][0] == "stokta":
1365
+ normal_price = f"Fiyat: {product_info[1][1]} TL"
1366
+ if product_info[1][3]:
1367
+ eft_price = f"Havale: {product_info[1][3]} TL"
1368
+ price_info = f"{normal_price}, {eft_price}"
1369
+ else:
1370
+ price_info = normal_price
1371
+
1372
+ new_msg = f"{product_info[2]} {product_info[1][0]} - {price_info}"
1373
+ else:
1374
+ new_msg = f"{product_info[2]} {product_info[1][0]}"
1375
+ system_messages.append({"role": "system", "content": new_msg})
1376
+ break
1377
+
1378
+ messages = system_messages + [{"role": "user", "content": user_message}]
1379
+
1380
+ if not OPENAI_API_KEY:
1381
+ return "OpenAI API anahtarı eksik. Lütfen environment variables'ları kontrol edin."
1382
+
1383
+ payload = {
1384
+ "model": "gpt-5-chat-latest",
1385
+ "messages": messages,
1386
+ "temperature": 0, # Deterministik cevaplar için
1387
+ "max_tokens": 800,
1388
+ "stream": False,
1389
+ "top_p": 0.1, # Daha tutarlı cevaplar için düşük değer
1390
+ "frequency_penalty": 0.1, # Tekrarları azaltmak için
1391
+ "presence_penalty": 0 # Yeni konulara açık olması için
1392
+ }
1393
+
1394
+ headers = {
1395
+ "Content-Type": "application/json",
1396
+ "Authorization": f"Bearer {OPENAI_API_KEY}"
1397
+ }
1398
+
1399
+ response = requests.post(API_URL, headers=headers, json=payload)
1400
+ if response.status_code == 200:
1401
+ result = response.json()
1402
+ return result['choices'][0]['message']['content']
1403
+ else:
1404
+ print(f"OpenAI API Error: {response.status_code} - {response.text}")
1405
+ return f"API hatası: {response.status_code}. Lütfen daha sonra tekrar deneyin."
1406
+
1407
+ except Exception as e:
1408
+ print(f"❌ WhatsApp mesaj işleme hatası: {e}")
1409
+ import traceback
1410
+ traceback.print_exc()
1411
+ logger.error(f"Detailed error: {str(e)}")
1412
+ logger.error(f"Error type: {type(e).__name__}")
1413
+ return "Teknik bir sorun oluştu. Lütfen daha sonra tekrar deneyin."
1414
+
1415
+ # FastAPI uygulaması
1416
+ app = FastAPI()
1417
+
1418
+ @app.post("/whatsapp-webhook")
1419
+ async def whatsapp_webhook(request: Request):
1420
+ try:
1421
+ form_data = await request.form()
1422
+
1423
+ from_number = form_data.get('From')
1424
+ to_number = form_data.get('To')
1425
+ message_body = form_data.get('Body')
1426
+ message_status = form_data.get('MessageStatus')
1427
+
1428
+ # Medya içeriği kontrolü
1429
+ num_media = form_data.get('NumMedia', '0')
1430
+ media_urls = []
1431
+ media_types = []
1432
+
1433
+ # Medya varsa URL'leri topla
1434
+ if num_media and int(num_media) > 0:
1435
+ for i in range(int(num_media)):
1436
+ media_url = form_data.get(f'MediaUrl{i}')
1437
+ media_type = form_data.get(f'MediaContentType{i}')
1438
+ if media_url:
1439
+ media_urls.append(media_url)
1440
+ media_types.append(media_type)
1441
+ logger.info(f"📸 Medya alındı: {media_type} - {media_url[:100]}...")
1442
+
1443
+ print(f"📱 Webhook - From: {from_number}, Body: {message_body}, Status: {message_status}")
1444
+
1445
+ # Durum güncellemelerini ignore et
1446
+ if message_status in ['sent', 'delivered', 'read', 'failed']:
1447
+ return {"status": "ignored", "message": f"Status: {message_status}"}
1448
+
1449
+ # Giden mesajları ignore et
1450
+ if to_number != TWILIO_WHATSAPP_NUMBER:
1451
+ return {"status": "ignored", "message": "Outgoing message"}
1452
+
1453
+ # Media Queue V2 İşleme
1454
+ if USE_MEDIA_QUEUE:
1455
+ if media_urls:
1456
+ # Medya mesajı geldi
1457
+ logger.info(f"📸 Media Queue V2: Medya alındı - {from_number}")
1458
+
1459
+ # Media Queue'ya ekle ve bekleme mesajı al
1460
+ wait_message = media_queue.handle_media(
1461
+ from_number,
1462
+ media_urls,
1463
+ media_types,
1464
+ message_body or ""
1465
+ )
1466
+
1467
+ # Bekleme mesajını gönder
1468
+ if twilio_client:
1469
+ twilio_client.messages.create(
1470
+ messaging_service_sid=TWILIO_MESSAGING_SERVICE_SID,
1471
+ body=wait_message,
1472
+ to=from_number
1473
+ )
1474
+ logger.info(f"📤 Bekleme mesajı gönderildi: {wait_message}")
1475
+
1476
+ return {"status": "media_queued", "message": wait_message}
1477
+
1478
+ else:
1479
+ # Metin mesajı geldi - cache'de medya var mı kontrol et
1480
+ combined_text, cached_media_urls, cached_media_types = media_queue.handle_text(from_number, message_body)
1481
+
1482
+ if combined_text and cached_media_urls:
1483
+ # Medya + metin birleştirildi
1484
+ logger.info(f"✅ Media Queue V2: Birleştirildi - {from_number}")
1485
+ logger.info(f" Birleşik mesaj: {combined_text[:100]}...")
1486
+ logger.info(f" Medya sayısı: {len(cached_media_urls)}")
1487
+
1488
+ # Birleştirilmiş mesajı işle
1489
+ message_body = combined_text
1490
+ media_urls = cached_media_urls
1491
+ media_types = cached_media_types
1492
+ # Aşağıdaki normal akışa devam et
1493
+ else:
1494
+ # Normal metin mesajı, cache'de medya yok
1495
+ logger.info(f"💬 Media Queue V2: Normal metin - {from_number}")
1496
+ # Normal akışa devam et
1497
+
1498
+ # Boş mesaj kontrolü
1499
+ if not message_body or message_body.strip() == "":
1500
+ if not media_urls: # Medya da yoksa ignore et
1501
+ return {"status": "ignored", "message": "Empty message"}
1502
+
1503
+ print(f"✅ MESAJ ALINDI: {from_number} -> {message_body}")
1504
+
1505
+ if not twilio_client:
1506
+ return {"status": "error", "message": "Twilio yapılandırması eksik"}
1507
+
1508
+ # HAFIZALİ MESAJ İŞLEME - Medya desteği ile
1509
+ if media_urls:
1510
+ # Medya varsa, görsel analiz yap
1511
+ ai_response = process_whatsapp_message_with_media(message_body, from_number, media_urls, media_types)
1512
+ else:
1513
+ # Normal metin mesajı işle
1514
+ ai_response = process_whatsapp_message_with_memory(message_body, from_number)
1515
+
1516
+ # Mesajı parçalara böl
1517
+ message_parts = split_long_message(ai_response, max_length=1600)
1518
+
1519
+ sent_messages = []
1520
+
1521
+ # Her parçayı sırayla gönder
1522
+ for i, part in enumerate(message_parts):
1523
+ if len(message_parts) > 1:
1524
+ if i == 0:
1525
+ part = f"{part}\n\n(1/{len(message_parts)})"
1526
+ elif i == len(message_parts) - 1:
1527
+ part = f"({i+1}/{len(message_parts)})\n\n{part}"
1528
+ else:
1529
+ part = f"({i+1}/{len(message_parts)})\n\n{part}"
1530
+
1531
+ # WhatsApp'a gönder
1532
+ message = twilio_client.messages.create(
1533
+ messaging_service_sid=TWILIO_MESSAGING_SERVICE_SID,
1534
+ body=part,
1535
+ to=from_number
1536
+ )
1537
+
1538
+ sent_messages.append(message.sid)
1539
+
1540
+ if i < len(message_parts) - 1:
1541
+ import time
1542
+ time.sleep(0.5)
1543
+
1544
+ print(f"✅ {len(message_parts)} PARÇA GÖNDERİLDİ")
1545
+
1546
+ # Debug için mevcut kategoriyi logla
1547
+ context = get_conversation_context(from_number)
1548
+ if context.get("current_category"):
1549
+ print(f"💭 Aktif kategori: {context['current_category']}")
1550
+
1551
+ return {
1552
+ "status": "success",
1553
+ "message_parts": len(message_parts),
1554
+ "message_sids": sent_messages,
1555
+ "current_category": context.get("current_category")
1556
+ }
1557
+
1558
+ except Exception as e:
1559
+ print(f"❌ Webhook hatası: {str(e)}")
1560
+ return {"status": "error", "message": str(e)}
1561
+
1562
+ @app.get("/")
1563
+ async def root():
1564
+ return {"message": "Trek WhatsApp Bot çalışıyor!", "status": "active"}
1565
+
1566
+ # Hafızayı temizleme endpoint'i
1567
+ @app.get("/clear-memory/{phone_number}")
1568
+ async def clear_memory(phone_number: str):
1569
+ """Belirli bir telefon numarasının hafızasını temizle"""
1570
+ if phone_number in conversation_memory:
1571
+ del conversation_memory[phone_number]
1572
+ return {"status": "success", "message": f"{phone_number} hafızası temizlendi"}
1573
+ return {"status": "info", "message": "Hafıza bulunamadı"}
1574
+
1575
+ # Mehmet Bey bildirimlerini görüntüleme endpoint'i
1576
+ @app.get("/mehmet-bey-notifications")
1577
+ async def view_mehmet_bey_notifications():
1578
+ """Mehmet Bey için kaydedilen bildirimleri görüntüle"""
1579
+ import json
1580
+ from datetime import datetime, timedelta
1581
+
1582
+ try:
1583
+ with open("mehmet_bey_notifications.json", "r") as f:
1584
+ notifications = json.load(f)
1585
+
1586
+ # Son 24 saatin bildirimlerini filtrele
1587
+ now = datetime.now()
1588
+ recent_notifications = []
1589
+
1590
+ for notif in notifications:
1591
+ notif_time = datetime.fromisoformat(notif["timestamp"])
1592
+ if now - notif_time < timedelta(hours=24):
1593
+ recent_notifications.append(notif)
1594
+
1595
+ # HTML formatında döndür
1596
+ html_content = """
1597
+ <html>
1598
+ <head>
1599
+ <title>Mehmet Bey Bildirimleri</title>
1600
+ <style>
1601
+ body { font-family: Arial; margin: 20px; background: #f5f5f5; }
1602
+ .notification {
1603
+ background: white;
1604
+ border: 1px solid #ddd;
1605
+ padding: 15px;
1606
+ margin: 10px 0;
1607
+ border-radius: 8px;
1608
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
1609
+ }
1610
+ .urgent { border-left: 5px solid #ff4444; }
1611
+ .medium { border-left: 5px solid #ffaa00; }
1612
+ .timestamp { color: #666; font-size: 0.9em; }
1613
+ .action {
1614
+ display: inline-block;
1615
+ padding: 3px 8px;
1616
+ border-radius: 4px;
1617
+ font-size: 0.85em;
1618
+ font-weight: bold;
1619
+ }
1620
+ .reserve { background: #ffebee; color: #c62828; }
1621
+ .price { background: #fff3e0; color: #f57c00; }
1622
+ .stock { background: #e3f2fd; color: #1976d2; }
1623
+ .test { background: #f3e5f5; color: #7b1fa2; }
1624
+ h1 { color: #333; }
1625
+ .stats {
1626
+ background: #2196F3;
1627
+ color: white;
1628
+ padding: 10px 20px;
1629
+ border-radius: 5px;
1630
+ display: inline-block;
1631
+ margin-bottom: 20px;
1632
+ }
1633
+ pre {
1634
+ background: #f9f9f9;
1635
+ padding: 10px;
1636
+ border-radius: 4px;
1637
+ white-space: pre-wrap;
1638
+ font-size: 0.9em;
1639
+ }
1640
+ </style>
1641
+ <meta charset="utf-8">
1642
+ </head>
1643
+ <body>
1644
+ <h1>🔔 Mehmet Bey Bildirim Sistemi</h1>
1645
+ <div class="stats">
1646
+ 📊 Toplam: """ + str(len(notifications)) + """ bildirim |
1647
+ 🕐 Son 24 saat: """ + str(len(recent_notifications)) + """ bildirim
1648
+ </div>
1649
+ """
1650
+
1651
+ # Son 20 bildirimi göster (en yeniden eskiye)
1652
+ for notif in reversed(notifications[-20:]):
1653
+ timestamp = notif.get('timestamp', '')
1654
+ action = notif.get('action', 'info')
1655
+ product = notif.get('product_name', 'Belirtilmemiş')
1656
+ customer = notif.get('customer_phone', '').replace('whatsapp:', '')
1657
+ message = notif.get('formatted_message', '')
1658
+
1659
+ # CSS class belirleme
1660
+ urgency_class = ''
1661
+ if 'reserve' in action or 'ayırt' in message.lower():
1662
+ urgency_class = 'urgent'
1663
+ elif 'price' in action or 'stock' in action:
1664
+ urgency_class = 'medium'
1665
+
1666
+ action_class = action
1667
+
1668
+ html_content += f'''
1669
+ <div class="notification {urgency_class}">
1670
+ <div class="timestamp">⏰ {timestamp}</div>
1671
+ <div style="margin-top: 10px;">
1672
+ <span class="action {action_class}">{action.upper()}</span>
1673
+ <strong>{product}</strong>
1674
+ </div>
1675
+ <div style="margin-top: 5px;">📱 Müşteri: {customer}</div>
1676
+ <pre>{message}</pre>
1677
+ </div>
1678
+ '''
1679
+
1680
+ html_content += """
1681
+ </body>
1682
+ </html>
1683
+ """
1684
+
1685
+ from fastapi.responses import HTMLResponse
1686
+ return HTMLResponse(content=html_content)
1687
+
1688
+ except FileNotFoundError:
1689
+ return HTMLResponse(content="""
1690
+ <html><body style="font-family: Arial; padding: 40px; text-align: center;">
1691
+ <h2>📭 Henüz bildirim yok</h2>
1692
+ <p>Müşteri mesajları geldiğinde burada görünecek.</p>
1693
+ </body></html>
1694
+ """)
1695
+ except Exception as e:
1696
+ return {"status": "error", "message": str(e)}
1697
+
1698
+ # Tüm hafızayı görme endpoint'i
1699
+ @app.get("/debug-memory")
1700
+ async def debug_memory():
1701
+ """Tüm hafızayı görüntüle (debug için)"""
1702
+ memory_info = {}
1703
+ for phone, context in conversation_memory.items():
1704
+ memory_info[phone] = {
1705
+ "current_category": context.get("current_category"),
1706
+ "message_count": len(context.get("messages", [])),
1707
+ "last_activity": str(context.get("last_activity"))
1708
+ }
1709
+ return {"conversation_memory": memory_info}
1710
+
1711
+ # Profil bilgilerini görme endpoint'i
1712
+ @app.get("/debug-profile/{phone_number}")
1713
+ async def debug_profile(phone_number: str):
1714
+ """Belirli kullanıcının profil bilgilerini görüntüle"""
1715
+ profile_summary = get_user_profile_summary(phone_number)
1716
+ return {"phone_number": phone_number, "profile": profile_summary}
1717
+
1718
+ # Tüm profilleri görme endpoint'i
1719
+ @app.get("/debug-profiles")
1720
+ async def debug_profiles():
1721
+ """Tüm kullanıcı profillerini görüntüle"""
1722
+ from whatsapp_passive_profiler import passive_profiler
1723
+ all_profiles = {}
1724
+ for phone_number in passive_profiler.profiles.keys():
1725
+ all_profiles[phone_number] = get_user_profile_summary(phone_number)
1726
+ return {"profiles": all_profiles}
1727
+
1728
+ @app.get("/health")
1729
+ async def health():
1730
+ return {
1731
+ "status": "healthy",
1732
+ "twilio_configured": twilio_client is not None,
1733
+ "openai_configured": OPENAI_API_KEY is not None,
1734
+ "products_loaded": len(products),
1735
+ "webhook_endpoint": "/whatsapp-webhook"
1736
+ }
1737
+
1738
+ @app.get("/test-madone")
1739
+ async def test_madone():
1740
+ """Test MADONE search directly"""
1741
+ from smart_warehouse_with_price import get_warehouse_stock_smart_with_price
1742
+
1743
+ # Set a test API key if needed
1744
+ import os
1745
+ if not os.getenv("OPENAI_API_KEY"):
1746
+ return {"error": "No OPENAI_API_KEY set"}
1747
+
1748
+ try:
1749
+ result = get_warehouse_stock_smart_with_price("madone sl 6")
1750
+ return {
1751
+ "query": "madone sl 6",
1752
+ "result": result if result else "No result",
1753
+ "api_key_set": bool(os.getenv("OPENAI_API_KEY"))
1754
+ }
1755
+ except Exception as e:
1756
+ return {"error": str(e), "type": type(e).__name__}
1757
+
1758
+ @app.post("/test-vision")
1759
+ async def test_vision(request: Request):
1760
+ """Test vision capabilities with a sample image URL"""
1761
+ try:
1762
+ data = await request.json()
1763
+ image_url = data.get("image_url")
1764
+ text = data.get("text", "Bu görselde ne var?")
1765
+
1766
+ if not image_url:
1767
+ return {"error": "image_url is required"}
1768
+
1769
+ # Test vision API
1770
+ messages = [
1771
+ {
1772
+ "role": "system",
1773
+ "content": "Sen bir bisiklet uzmanısın. Görselleri analiz et ve detaylı bilgi ver."
1774
+ },
1775
+ {
1776
+ "role": "user",
1777
+ "content": [
1778
+ {"type": "text", "text": text},
1779
+ {"type": "image_url", "image_url": {"url": image_url}}
1780
+ ]
1781
+ }
1782
+ ]
1783
+
1784
+ payload = {
1785
+ "model": "gpt-5-chat-latest",
1786
+ "messages": messages,
1787
+ "temperature": 0, # Deterministik cevaplar için
1788
+ "max_tokens": 500,
1789
+ "stream": False,
1790
+ "top_p": 0.1, # Daha tutarlı cevaplar için düşük değer
1791
+ "frequency_penalty": 0.1, # Tekrarları azaltmak için
1792
+ "presence_penalty": 0 # Yeni konulara açık olması için
1793
+ }
1794
+
1795
+ headers = {
1796
+ "Content-Type": "application/json",
1797
+ "Authorization": f"Bearer {OPENAI_API_KEY}"
1798
+ }
1799
+
1800
+ response = requests.post(API_URL, headers=headers, json=payload)
1801
+
1802
+ if response.status_code == 200:
1803
+ result = response.json()
1804
+ return {
1805
+ "success": True,
1806
+ "response": result['choices'][0]['message']['content'],
1807
+ "model": result.get('model', 'unknown')
1808
+ }
1809
+ else:
1810
+ return {
1811
+ "success": False,
1812
+ "error": response.text,
1813
+ "status_code": response.status_code
1814
+ }
1815
+
1816
+ except Exception as e:
1817
+ return {"error": str(e), "type": type(e).__name__}
1818
+
1819
+ if __name__ == "__main__":
1820
+ import uvicorn
1821
+ print("🚀 Trek WhatsApp Bot başlatılıyor...")
1822
+ uvicorn.run(app, host="0.0.0.0", port=7860)
follow_up_system_calismiyor_20250903_2223.py ADDED
@@ -0,0 +1,407 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ Otomatik Takip ve Hatırlatma Sistemi
6
+ Müşteri taahhütlerini takip eder ve Mehmet Bey'e hatırlatır
7
+ """
8
+
9
+ import os
10
+ import json
11
+ import logging
12
+ from datetime import datetime, timedelta
13
+ from typing import Optional, List, Dict
14
+ from dataclasses import dataclass, asdict
15
+ from enum import Enum
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ # Takip tipi
20
+ class FollowUpType(Enum):
21
+ RESERVATION = "reservation" # Ayırtma takibi
22
+ VISIT_PROMISE = "visit" # Gelme sözü takibi
23
+ PRICE_INQUIRY = "price" # Fiyat sorusu takibi
24
+ DECISION_PENDING = "decision" # Karar bekleyen
25
+ TEST_RIDE = "test_ride" # Test sürüşü
26
+
27
+ # Takip durumu
28
+ class FollowUpStatus(Enum):
29
+ PENDING = "pending" # Bekliyor
30
+ REMINDED = "reminded" # Hatırlatma yapıldı
31
+ COMPLETED = "completed" # Tamamlandı
32
+ CANCELLED = "cancelled" # İptal edildi
33
+
34
+ @dataclass
35
+ class FollowUp:
36
+ """Takip kaydı"""
37
+ id: str # Unique ID
38
+ customer_phone: str # Müşteri telefonu
39
+ customer_name: Optional[str] # Müşteri adı
40
+ product_name: str # Ürün
41
+ follow_up_type: str # Takip tipi
42
+ status: str # Durum
43
+ created_at: str # Oluşturulma zamanı
44
+ follow_up_at: str # Hatırlatma zamanı
45
+ original_message: str # Orijinal mesaj
46
+ notes: Optional[str] # Notlar
47
+ store_name: Optional[str] # Mağaza
48
+ reminded_count: int = 0 # Kaç kez hatırlatıldı
49
+
50
+ class FollowUpManager:
51
+ """Takip yöneticisi"""
52
+
53
+ def __init__(self, db_file: str = "follow_ups.json"):
54
+ self.db_file = db_file
55
+ self.follow_ups = self._load_database()
56
+
57
+ def _load_database(self) -> List[FollowUp]:
58
+ """Database'i yükle"""
59
+ if os.path.exists(self.db_file):
60
+ try:
61
+ with open(self.db_file, "r") as f:
62
+ data = json.load(f)
63
+ return [FollowUp(**item) for item in data]
64
+ except Exception as e:
65
+ logger.error(f"Database yükleme hatası: {e}")
66
+ return []
67
+ return []
68
+
69
+ def _save_database(self):
70
+ """Database'i kaydet"""
71
+ try:
72
+ data = [asdict(f) for f in self.follow_ups]
73
+ with open(self.db_file, "w") as f:
74
+ json.dump(data, f, indent=2, ensure_ascii=False)
75
+ except Exception as e:
76
+ logger.error(f"Database kayıt hatası: {e}")
77
+
78
+ def create_follow_up(
79
+ self,
80
+ customer_phone: str,
81
+ product_name: str,
82
+ follow_up_type: FollowUpType,
83
+ original_message: str,
84
+ follow_up_hours: int = 24,
85
+ customer_name: Optional[str] = None,
86
+ notes: Optional[str] = None,
87
+ store_name: Optional[str] = None
88
+ ) -> FollowUp:
89
+ """Yeni takip oluştur"""
90
+
91
+ now = datetime.now()
92
+ follow_up_time = now + timedelta(hours=follow_up_hours)
93
+
94
+ # Unique ID oluştur
95
+ follow_up_id = f"{customer_phone}_{now.strftime('%Y%m%d_%H%M%S')}"
96
+
97
+ follow_up = FollowUp(
98
+ id=follow_up_id,
99
+ customer_phone=customer_phone,
100
+ customer_name=customer_name,
101
+ product_name=product_name,
102
+ follow_up_type=follow_up_type.value,
103
+ status=FollowUpStatus.PENDING.value,
104
+ created_at=now.isoformat(),
105
+ follow_up_at=follow_up_time.isoformat(),
106
+ original_message=original_message,
107
+ notes=notes,
108
+ store_name=store_name,
109
+ reminded_count=0
110
+ )
111
+
112
+ self.follow_ups.append(follow_up)
113
+ self._save_database()
114
+
115
+ logger.info(f"✅ Takip oluşturuldu: {follow_up_id}")
116
+ logger.info(f" Hatırlatma zamanı: {follow_up_time.strftime('%d.%m.%Y %H:%M')}")
117
+
118
+ return follow_up
119
+
120
+ def get_pending_reminders(self) -> List[FollowUp]:
121
+ """Bekleyen hatırlatmaları getir"""
122
+ now = datetime.now()
123
+ pending = []
124
+
125
+ for follow_up in self.follow_ups:
126
+ if follow_up.status == FollowUpStatus.PENDING.value:
127
+ follow_up_time = datetime.fromisoformat(follow_up.follow_up_at)
128
+ if follow_up_time <= now:
129
+ pending.append(follow_up)
130
+
131
+ return pending
132
+
133
+ def mark_as_reminded(self, follow_up_id: str):
134
+ """Hatırlatma yapıldı olarak işaretle"""
135
+ for follow_up in self.follow_ups:
136
+ if follow_up.id == follow_up_id:
137
+ follow_up.status = FollowUpStatus.REMINDED.value
138
+ follow_up.reminded_count += 1
139
+ self._save_database()
140
+ logger.info(f"✅ Hatırlatma yapıldı: {follow_up_id}")
141
+ break
142
+
143
+ def mark_as_completed(self, follow_up_id: str):
144
+ """Tamamlandı olarak işaretle"""
145
+ for follow_up in self.follow_ups:
146
+ if follow_up.id == follow_up_id:
147
+ follow_up.status = FollowUpStatus.COMPLETED.value
148
+ self._save_database()
149
+ logger.info(f"✅ Takip tamamlandı: {follow_up_id}")
150
+ break
151
+
152
+ def get_customer_history(self, customer_phone: str) -> List[FollowUp]:
153
+ """Müşteri geçmişini getir"""
154
+ return [f for f in self.follow_ups if f.customer_phone == customer_phone]
155
+
156
+ def get_todays_follow_ups(self) -> List[FollowUp]:
157
+ """Bugünün takiplerini getir"""
158
+ today = datetime.now().date()
159
+ todays = []
160
+
161
+ for follow_up in self.follow_ups:
162
+ follow_up_date = datetime.fromisoformat(follow_up.follow_up_at).date()
163
+ if follow_up_date == today:
164
+ todays.append(follow_up)
165
+
166
+ return todays
167
+
168
+ def analyze_message_for_follow_up(message: str) -> Optional[Dict]:
169
+ """
170
+ Mesajı analiz et ve takip gerekip gerekmediğini belirle
171
+
172
+ Returns:
173
+ {
174
+ "needs_follow_up": True/False,
175
+ "follow_up_type": FollowUpType,
176
+ "follow_up_hours": int,
177
+ "reason": str
178
+ }
179
+ """
180
+
181
+ message_lower = message.lower()
182
+
183
+ # YARIN gelme sözleri (24 saat sonra hatırlat)
184
+ tomorrow_keywords = [
185
+ 'yarın', 'yarin',
186
+ 'yarın gel', 'yarın al', 'yarın uğra',
187
+ 'yarına', 'yarina',
188
+ 'ertesi gün'
189
+ ]
190
+
191
+ # BUGÜN gelme sözleri (6 saat sonra hatırlat)
192
+ today_keywords = [
193
+ 'bugün', 'bugun',
194
+ 'bugün gel', 'bugün al', 'bugün uğra',
195
+ 'akşam gel', 'aksam gel',
196
+ 'öğleden sonra', 'ogleden sonra',
197
+ 'birazdan', 'biraz sonra',
198
+ '1 saat', 'bir saat',
199
+ '2 saat', 'iki saat',
200
+ '30 dakika', 'yarım saat'
201
+ ]
202
+
203
+ # HAFTA SONU gelme sözleri
204
+ weekend_keywords = [
205
+ 'hafta sonu', 'haftasonu',
206
+ 'cumartesi', 'pazar',
207
+ 'hafta içi', 'haftaiçi'
208
+ ]
209
+
210
+ # KARAR VERME sözleri (48 saat sonra hatırlat)
211
+ decision_keywords = [
212
+ 'düşüneyim', 'düşüncem', 'düşünelim',
213
+ 'danışayım', 'danısayım', 'danışıcam',
214
+ 'eşime sor', 'eşimle konuş', 'esime sor',
215
+ 'karar ver', 'haber ver',
216
+ 'araştırayım', 'araştırıcam',
217
+ 'bakarım', 'bakarız', 'bakıcam'
218
+ ]
219
+
220
+ # TEST SÜRÜŞÜ (4 saat sonra hatırlat)
221
+ test_keywords = [
222
+ 'test sürüş', 'test et', 'dene',
223
+ 'binebilir', 'binmek ist',
224
+ 'test ride', 'deneme sürüş'
225
+ ]
226
+
227
+ # Yarın kontrolü
228
+ for keyword in tomorrow_keywords:
229
+ if keyword in message_lower:
230
+ return {
231
+ "needs_follow_up": True,
232
+ "follow_up_type": FollowUpType.VISIT_PROMISE,
233
+ "follow_up_hours": 24,
234
+ "reason": f"Müşteri yarın geleceğini söyledi: '{keyword}'"
235
+ }
236
+
237
+ # Bugün kontrolü
238
+ for keyword in today_keywords:
239
+ if keyword in message_lower:
240
+ return {
241
+ "needs_follow_up": True,
242
+ "follow_up_type": FollowUpType.VISIT_PROMISE,
243
+ "follow_up_hours": 6,
244
+ "reason": f"Müşteri bugün geleceğini söyledi: '{keyword}'"
245
+ }
246
+
247
+ # Hafta sonu kontrolü
248
+ for keyword in weekend_keywords:
249
+ if keyword in message_lower:
250
+ # Bugün hangi gün?
251
+ today = datetime.now().weekday() # 0=Pazartesi, 6=Pazar
252
+ if today < 5: # Hafta içiyse
253
+ days_to_saturday = 5 - today
254
+ hours = days_to_saturday * 24
255
+ else: # Zaten hafta sonuysa
256
+ hours = 24
257
+
258
+ return {
259
+ "needs_follow_up": True,
260
+ "follow_up_type": FollowUpType.VISIT_PROMISE,
261
+ "follow_up_hours": hours,
262
+ "reason": f"Müşteri hafta sonu geleceğini söyledi: '{keyword}'"
263
+ }
264
+
265
+ # Karar verme kontrolü
266
+ for keyword in decision_keywords:
267
+ if keyword in message_lower:
268
+ return {
269
+ "needs_follow_up": True,
270
+ "follow_up_type": FollowUpType.DECISION_PENDING,
271
+ "follow_up_hours": 48,
272
+ "reason": f"Müşteri düşüneceğini söyledi: '{keyword}'"
273
+ }
274
+
275
+ # Test sürüşü kontrolü
276
+ for keyword in test_keywords:
277
+ if keyword in message_lower:
278
+ return {
279
+ "needs_follow_up": True,
280
+ "follow_up_type": FollowUpType.TEST_RIDE,
281
+ "follow_up_hours": 4,
282
+ "reason": f"Müşteri test sürüşü istiyor: '{keyword}'"
283
+ }
284
+
285
+ # Ayırtma varsa 24 saat sonra kontrol
286
+ reservation_keywords = ['ayırt', 'rezerve', 'tutun', 'sakla']
287
+ for keyword in reservation_keywords:
288
+ if keyword in message_lower:
289
+ return {
290
+ "needs_follow_up": True,
291
+ "follow_up_type": FollowUpType.RESERVATION,
292
+ "follow_up_hours": 24,
293
+ "reason": f"Ayırtma talebi var: '{keyword}'"
294
+ }
295
+
296
+ return None
297
+
298
+ def format_reminder_message(follow_up: FollowUp) -> str:
299
+ """Hatırlatma mesajını formatla"""
300
+
301
+ # Takip tipine göre emoji
302
+ type_emojis = {
303
+ "reservation": "📦",
304
+ "visit": "🚶",
305
+ "price": "💰",
306
+ "decision": "🤔",
307
+ "test_ride": "🚴"
308
+ }
309
+
310
+ emoji = type_emojis.get(follow_up.follow_up_type, "📌")
311
+
312
+ # Zaman hesaplama
313
+ created_time = datetime.fromisoformat(follow_up.created_at)
314
+ time_diff = datetime.now() - created_time
315
+
316
+ if time_diff.days > 0:
317
+ time_str = f"{time_diff.days} gün önce"
318
+ elif time_diff.seconds > 3600:
319
+ hours = time_diff.seconds // 3600
320
+ time_str = f"{hours} saat önce"
321
+ else:
322
+ time_str = "Az önce"
323
+
324
+ # Mesaj oluştur
325
+ message = f"""
326
+ {emoji} **TAKİP HATIRLATMASI**
327
+
328
+ 👤 Müşteri: {follow_up.customer_name or "İsimsiz"}
329
+ 📱 Tel: {follow_up.customer_phone.replace('whatsapp:', '')}
330
+
331
+ 🚲 Ürün: {follow_up.product_name}
332
+ ⏰ İlk mesaj: {time_str}
333
+
334
+ 📝 Müşteri mesajı:
335
+ "{follow_up.original_message}"
336
+ """
337
+
338
+ # Tipe göre özel mesaj
339
+ if follow_up.follow_up_type == "reservation":
340
+ message += "\n⚠️ AYIRTMA TAKİBİ: Müşteri geldi mi?"
341
+ elif follow_up.follow_up_type == "visit":
342
+ message += "\n⚠️ ZİYARET TAKİBİ: Müşteri geleceğini söylemişti"
343
+ elif follow_up.follow_up_type == "decision":
344
+ message += "\n⚠️ KARAR TAKİBİ: Müşteri düşüneceğini söylemişti"
345
+ elif follow_up.follow_up_type == "test_ride":
346
+ message += "\n⚠️ TEST SÜRÜŞÜ TAKİBİ: Test için gelecekti"
347
+
348
+ message += "\n\n📞 Müşteriyi arayıp durumu öğrenin!"
349
+
350
+ return message
351
+
352
+ # Test fonksiyonu
353
+ def test_follow_up_system():
354
+ """Test senaryoları"""
355
+
356
+ print("\n" + "="*60)
357
+ print("TAKİP SİSTEMİ TESTİ")
358
+ print("="*60)
359
+
360
+ manager = FollowUpManager("test_follow_ups.json")
361
+
362
+ # Test mesajları
363
+ test_messages = [
364
+ ("Yarın gelip FX 2'yi alacağım", "FX 2"),
365
+ ("Eşime danışayım, size dönerim", "Marlin 5"),
366
+ ("Bugün akşam uğrarım", "Checkpoint"),
367
+ ("Hafta sonu test sürüşü yapabilir miyim?", "Domane"),
368
+ ("30 dakikaya oradayım, ayırtın", "FX Sport")
369
+ ]
370
+
371
+ for message, product in test_messages:
372
+ print(f"\n📝 Mesaj: '{message}'")
373
+
374
+ analysis = analyze_message_for_follow_up(message)
375
+ if analysis and analysis["needs_follow_up"]:
376
+ print(f"✅ Takip gerekli!")
377
+ print(f" Tip: {analysis['follow_up_type'].value}")
378
+ print(f" {analysis['follow_up_hours']} saat sonra hatırlat")
379
+ print(f" Sebep: {analysis['reason']}")
380
+
381
+ # Takip oluştur
382
+ follow_up = manager.create_follow_up(
383
+ customer_phone="whatsapp:+905551234567",
384
+ product_name=product,
385
+ follow_up_type=analysis["follow_up_type"],
386
+ original_message=message,
387
+ follow_up_hours=analysis["follow_up_hours"]
388
+ )
389
+ else:
390
+ print("❌ Takip gerekmiyor")
391
+
392
+ # Bekleyen hatırlatmaları göster
393
+ print("\n" + "="*60)
394
+ print("BEKLEYEN HATIRLATMALAR")
395
+ print("="*60)
396
+
397
+ pending = manager.get_pending_reminders()
398
+ if pending:
399
+ for f in pending:
400
+ print(f"\n{format_reminder_message(f)}")
401
+ else:
402
+ print("Bekleyen hatırlatma yok")
403
+
404
+ print("\n" + "="*60)
405
+
406
+ if __name__ == "__main__":
407
+ test_follow_up_system()
follow_ups.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "whatsapp:+905551234567_20250903_185211",
4
+ "customer_phone": "whatsapp:+905551234567",
5
+ "customer_name": null,
6
+ "product_name": "FX 2",
7
+ "follow_up_type": "visit",
8
+ "status": "pending",
9
+ "created_at": "2025-09-03T18:52:11.000365",
10
+ "follow_up_at": "2025-09-04T18:52:11.000365",
11
+ "original_message": "Yarın öğlen gelip FX 2yi alacağım",
12
+ "notes": "Müşteri yarın geleceğini söyledi: 'yarın'",
13
+ "store_name": null,
14
+ "reminded_count": 0
15
+ }
16
+ ]
mehmet_bey_notifications.json ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "timestamp": "2025-09-03T17:18:18.885471",
4
+ "customer_phone": "whatsapp:+905551234567",
5
+ "customer_name": "Test Müşteri",
6
+ "product_name": "FX 2 (Kırmızı, L Beden)",
7
+ "action": "test",
8
+ "store_name": "merkez",
9
+ "additional_info": "Bu bir test bildirimidir. Sistem çalışıyor.",
10
+ "formatted_message": "🧪 *TEST DENEMESİ*\n📅 03.09.2025 17:18\n\n👤 *Müşteri:*\n İsim: Test Müşteri\n Tel: +905551234567\n\n🚲 *Ürün:* FX 2 (Kırmızı, L Beden)\n🏪 *Mağaza:* Merkez\n\n📝 *Not:*\nBu bir test bildirimidir. Sistem çalışıyor.\n\n---\nTrek WhatsApp Bot\n📍 Alsancak Mağaza - Mehmet Bey"
11
+ },
12
+ {
13
+ "timestamp": "2025-09-03T17:27:28.161577",
14
+ "customer_phone": "whatsapp:+905551234567",
15
+ "customer_name": "Test Müşteri",
16
+ "product_name": "FX 2 (Kırmızı, L Beden)",
17
+ "action": "test",
18
+ "store_name": "merkez",
19
+ "additional_info": "Bu bir test bildirimidir. Sistem çalışıyor.",
20
+ "formatted_message": "🧪 *TEST DENEMESİ*\n📅 03.09.2025 17:27\n\n👤 *Müşteri:*\n İsim: Test Müşteri\n Tel: +905551234567\n\n🚲 *Ürün:* FX 2 (Kırmızı, L Beden)\n🏪 *Mağaza:* Merkez\n\n📝 *Not:*\nBu bir test bildirimidir. Sistem çalışıyor.\n\n---\nTrek WhatsApp Bot\n📍 Alsancak Mağaza - Mehmet Bey"
21
+ },
22
+ {
23
+ "timestamp": "2025-09-03T17:27:55.425393",
24
+ "customer_phone": "whatsapp:+905321234567",
25
+ "customer_name": "Test Müşteri",
26
+ "product_name": "Marlin 5",
27
+ "action": "reserve",
28
+ "store_name": "alsancak",
29
+ "additional_info": "Müşteri yarın gelip almak istiyor. ACİL!",
30
+ "formatted_message": "🔔 *YENİ AYIRTMA TALEBİ*\n📅 03.09.2025 17:27\n\n👤 *Müşteri:*\n İsim: Test Müşteri\n Tel: +905321234567\n\n🚲 *Ürün:* Marlin 5\n🏪 *Mağaza:* Alsancak\n\n📝 *Not:*\nMüşteri yarın gelip almak istiyor. ACİL!\n\n✅ *Yapılacaklar:*\n1. Ürün stok kontrolü\n2. Müşteriyi arayın\n3. Ödeme/teslimat planı\n\n---\nTrek WhatsApp Bot\n📍 Alsancak Mağaza - Mehmet Bey"
31
+ },
32
+ {
33
+ "timestamp": "2025-09-03T17:44:26.365440",
34
+ "customer_phone": "whatsapp:+905436362335",
35
+ "customer_name": "Önemli Müşteri",
36
+ "product_name": "Marlin 7",
37
+ "action": "reserve",
38
+ "store_name": "alsancak",
39
+ "additional_info": "Müşteri hemen almak istiyor. YÜKSEK ÖNCELİK!",
40
+ "formatted_message": "🔔 *YENİ AYIRTMA TALEBİ*\n📅 03.09.2025 17:44\n\n👤 *Müşteri:*\n İsim: Önemli Müşteri\n Tel: +905436362335\n\n🚲 *Ürün:* Marlin 7\n🏪 *Mağaza:* Alsancak\n\n📝 *Not:*\nMüşteri hemen almak istiyor. YÜKSEK ÖNCELİK!\n\n✅ *Yapılacaklar:*\n1. Ürün stok kontrolü\n2. Müşteriyi arayın\n3. Ödeme/teslimat planı\n\n---\nTrek WhatsApp Bot\n📍 Alsancak Mağaza - Mehmet Bey"
41
+ },
42
+ {
43
+ "timestamp": "2025-09-03T17:54:37.750453",
44
+ "customer_phone": "whatsapp:+905551234567",
45
+ "customer_name": "Ahmet Yılmaz",
46
+ "product_name": "Marlin 5",
47
+ "action": "reserve",
48
+ "store_name": "alsancak",
49
+ "additional_info": "Müşteri yarın sabah gelecek. Bisikleti hazır tutun!",
50
+ "formatted_message": "🔔 *YENİ AYIRTMA TALEBİ*\n📅 03.09.2025 17:54\n\n👤 *Müşteri:*\n İsim: Ahmet Yılmaz\n Tel: +905551234567\n\n🚲 *Ürün:* Marlin 5\n🏪 *Mağaza:* Alsancak\n\n📝 *Not:*\nMüşteri yarın sabah gelecek. Bisikleti hazır tutun!\n\n✅ *Yapılacaklar:*\n1. Ürün stok kontrolü\n2. Müşteriyi arayın\n3. Ödeme/teslimat planı\n\n---\nTrek WhatsApp Bot\n📍 Alsancak Mağaza - Mehmet Bey"
51
+ },
52
+ {
53
+ "timestamp": "2025-09-03T17:57:59.269764",
54
+ "customer_phone": "whatsapp:+905321112233",
55
+ "customer_name": "Önemli Müşteri",
56
+ "product_name": "FX 2",
57
+ "action": "reserve",
58
+ "store_name": "alsancak",
59
+ "additional_info": "Müşteri 30 dakika içinde gelecek. HAZIR TUTUN!",
60
+ "formatted_message": "🔔 *YENİ AYIRTMA TALEBİ*\n📅 03.09.2025 17:57\n\n👤 *Müşteri:*\n İsim: Önemli Müşteri\n Tel: +905321112233\n\n🚲 *Ürün:* FX 2\n🏪 *Mağaza:* Alsancak\n\n📝 *Not:*\nMüşteri 30 dakika içinde gelecek. HAZIR TUTUN!\n\n✅ *Yapılacaklar:*\n1. Ürün stok kontrolü\n2. Müşteriyi arayın\n3. Ödeme/teslimat planı\n\n---\nTrek WhatsApp Bot\n📍 Alsancak Mağaza - Mehmet Bey"
61
+ },
62
+ {
63
+ "timestamp": "2025-09-03T18:02:38.518449",
64
+ "customer_phone": "whatsapp:+905551234567",
65
+ "customer_name": "Test Müşteri",
66
+ "product_name": "Marlin 7 Gen 3",
67
+ "action": "reserve",
68
+ "store_name": "alsancak",
69
+ "additional_info": "🧪 TEST MESAJI - Sistem kontrolü yapılıyor. Bu bir test bildirimidir.",
70
+ "formatted_message": "🔔 *YENİ AYIRTMA TALEBİ*\n📅 03.09.2025 18:02\n\n👤 *Müşteri:*\n İsim: Test Müşteri\n Tel: +905551234567\n\n🚲 *Ürün:* Marlin 7 Gen 3\n🏪 *Mağaza:* Alsancak\n\n📝 *Not:*\n🧪 TEST MESAJI - Sistem kontrolü yapılıyor. Bu bir test bildirimidir.\n\n✅ *Yapılacaklar:*\n1. Ürün stok kontrolü\n2. Müşteriyi arayın\n3. Ödeme/teslimat planı\n\n---\nTrek WhatsApp Bot\n📍 Alsancak Mağaza - Mehmet Bey"
71
+ },
72
+ {
73
+ "timestamp": "2025-09-03T18:19:23.249730",
74
+ "customer_phone": "whatsapp:+905325323232",
75
+ "customer_name": null,
76
+ "product_name": "FX 2",
77
+ "action": "reserve",
78
+ "store_name": "alsancak",
79
+ "additional_info": "Müşteri WhatsApp üzerinden yazdı: \"FX 2 var mı? Yarın öğlen gelip alabilir miyim? Ayırtırsanız sevinirim.\"",
80
+ "formatted_message": "🔔 *YENİ AYIRTMA TALEBİ*\n📅 03.09.2025 18:19\n\n👤 *Müşteri:*\n Tel: +905325323232\n\n🚲 *Ürün:* FX 2\n🏪 *Mağaza:* Alsancak\n\n📝 *Not:*\nMüşteri WhatsApp üzerinden yazdı: \"FX 2 var mı? Yarın öğlen gelip alabilir miyim? Ayırtırsanız sevinirim.\"\n\n✅ *Yapılacaklar:*\n1. Ürün stok kontrolü\n2. Müşteriyi arayın\n3. Ödeme/teslimat planı\n\n---\nTrek WhatsApp Bot\n📍 Alsancak Mağaza - Mehmet Bey"
81
+ },
82
+ {
83
+ "timestamp": "2025-09-03T18:22:29.645777",
84
+ "customer_phone": "whatsapp:+905325323232",
85
+ "customer_name": "Ali Veli",
86
+ "product_name": "FX 2",
87
+ "action": "reserve",
88
+ "store_name": "alsancak",
89
+ "additional_info": "Müşteri mesajı: \"FX 2 yi yarın alabilirim, ayırtın lütfen\"",
90
+ "formatted_message": "🔔 *YENİ AYIRTMA TALEBİ*\n📅 03.09.2025 18:22\n\n👤 *Müşteri:*\n İsim: Ali Veli\n Tel: +905325323232\n\n🚲 *Ürün:* FX 2\n🏪 *Mağaza:* Alsancak\n\n📝 *Not:*\nMüşteri mesajı: \"FX 2 yi yarın alabilirim, ayırtın lütfen\"\n\n✅ *Yapılacaklar:*\n1. Ürün stok kontrolü\n2. Müşteriyi arayın\n3. Ödeme/teslimat planı\n\n---\nTrek WhatsApp Bot\n📍 Alsancak Mağaza - Mehmet Bey"
91
+ },
92
+ {
93
+ "timestamp": "2025-09-03T18:52:11.000551",
94
+ "customer_phone": "whatsapp:+905551234567",
95
+ "customer_name": "Test Müşteri",
96
+ "product_name": "FX 2",
97
+ "action": "reserve",
98
+ "store_name": "alsancak",
99
+ "additional_info": "TEST: Takip sistemi çalışıyor. Yarın hatırlatma gelecek!",
100
+ "formatted_message": "🔔 *YENİ AYIRTMA TALEBİ*\n📅 03.09.2025 18:52\n\n👤 *Müşteri:*\n İsim: Test Müşteri\n Tel: +905551234567\n\n🚲 *Ürün:* FX 2\n🏪 *Mağaza:* Alsancak\n\n📝 *Not:*\nTEST: Takip sistemi çalışıyor. Yarın hatırlatma gelecek!\n\n✅ *Yapılacaklar:*\n1. Ürün stok kontrolü\n2. Müşteriyi arayın\n3. Ödeme/teslimat planı\n\n---\nTrek WhatsApp Bot\n📍 Alsancak Mağaza - Mehmet Bey"
101
+ }
102
+ ]
reminder_scheduler_calismiyor_20250903_2223.py ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ Otomatik Hatırlatma Zamanlayıcısı
6
+ Belirli aralıklarla takipleri kontrol eder ve hatırlatma gönderir
7
+ """
8
+
9
+ import os
10
+ import time
11
+ import logging
12
+ from datetime import datetime
13
+ from typing import List
14
+
15
+ # Import our modules
16
+ from follow_up_system import FollowUpManager, format_reminder_message
17
+ from store_notification import send_store_notification
18
+
19
+ logging.basicConfig(
20
+ level=logging.INFO,
21
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
22
+ )
23
+ logger = logging.getLogger(__name__)
24
+
25
+ class ReminderScheduler:
26
+ """Hatırlatma zamanlayıcısı"""
27
+
28
+ def __init__(self, check_interval_minutes: int = 10):
29
+ """
30
+ Args:
31
+ check_interval_minutes: Kaç dakikada bir kontrol yapılacak
32
+ """
33
+ self.check_interval = check_interval_minutes * 60 # Saniyeye çevir
34
+ self.manager = FollowUpManager()
35
+ self.running = False
36
+
37
+ def send_reminder(self, follow_up) -> bool:
38
+ """Hatırlatma mesajı gönder"""
39
+
40
+ try:
41
+ # Hatırlatma mesajını hazırla
42
+ reminder_message = format_reminder_message(follow_up)
43
+
44
+ # Mehmet Bey'e bildirim gönder
45
+ result = send_store_notification(
46
+ customer_phone=follow_up.customer_phone,
47
+ customer_name=follow_up.customer_name,
48
+ product_name=follow_up.product_name,
49
+ action="reminder", # Yeni tip: hatırlatma
50
+ store_name=follow_up.store_name,
51
+ additional_info=f"⏰ TAKİP HATIRLATMASI: {follow_up.original_message}"
52
+ )
53
+
54
+ if result:
55
+ logger.info(f"✅ Hatırlatma gönderildi: {follow_up.id}")
56
+ return True
57
+ else:
58
+ logger.error(f"❌ Hatırlatma gönderilemedi: {follow_up.id}")
59
+ return False
60
+
61
+ except Exception as e:
62
+ logger.error(f"Hatırlatma gönderme hatası: {e}")
63
+ return False
64
+
65
+ def check_and_send_reminders(self):
66
+ """Bekleyen hatırlatmaları kontrol et ve gönder"""
67
+
68
+ logger.info("🔍 Hatırlatmalar kontrol ediliyor...")
69
+
70
+ # Bekleyen hatırlatmaları al
71
+ pending = self.manager.get_pending_reminders()
72
+
73
+ if not pending:
74
+ logger.info("📭 Bekleyen hatırlatma yok")
75
+ return
76
+
77
+ logger.info(f"📬 {len(pending)} hatırlatma bulundu")
78
+
79
+ # Her birini gönder
80
+ for follow_up in pending:
81
+ logger.info(f"📤 Hatırlatma gönderiliyor: {follow_up.customer_phone}")
82
+
83
+ if self.send_reminder(follow_up):
84
+ # Başarılıysa durumu güncelle
85
+ self.manager.mark_as_reminded(follow_up.id)
86
+
87
+ # Eğer çok kez hatırlatma yapıldıysa tamamlanmış say
88
+ if follow_up.reminded_count >= 2:
89
+ self.manager.mark_as_completed(follow_up.id)
90
+ logger.info(f"✅ Takip tamamlandı (2 hatırlatma yapıldı): {follow_up.id}")
91
+
92
+ # Mesajlar arası bekle (rate limit)
93
+ time.sleep(2)
94
+
95
+ def send_daily_summary(self):
96
+ """Günlük özet gönder"""
97
+
98
+ now = datetime.now()
99
+ todays = self.manager.get_todays_follow_ups()
100
+
101
+ if not todays:
102
+ return
103
+
104
+ # Özet mesajı hazırla
105
+ summary = f"""
106
+ 📊 **GÜNLÜK TAKİP ÖZETİ**
107
+ 📅 {now.strftime('%d.%m.%Y')}
108
+
109
+ Bugün takip edilmesi gereken {len(todays)} müşteri var:
110
+ """
111
+
112
+ for i, follow_up in enumerate(todays, 1):
113
+ status_emoji = "✅" if follow_up.status == "completed" else "⏳"
114
+ summary += f"""
115
+ {i}. {status_emoji} {follow_up.customer_phone.replace('whatsapp:', '')}
116
+ Ürün: {follow_up.product_name}
117
+ Durum: {follow_up.status}
118
+ """
119
+
120
+ summary += "\n📞 Takipleri tamamlamayı unutmayın!"
121
+
122
+ # Özet bildirimi gönder
123
+ send_store_notification(
124
+ customer_phone="whatsapp:+905439362335", # Direkt Mehmet Bey
125
+ customer_name="Sistem",
126
+ product_name="Günlük Özet",
127
+ action="info",
128
+ additional_info=summary
129
+ )
130
+
131
+ logger.info("📊 Günlük özet gönderildi")
132
+
133
+ def run(self):
134
+ """Zamanlayıcıyı başlat"""
135
+
136
+ self.running = True
137
+ logger.info(f"⏰ Hatırlatma zamanlayıcısı başlatıldı")
138
+ logger.info(f" Kontrol aralığı: {self.check_interval_minutes} dakika")
139
+
140
+ last_summary_date = None
141
+
142
+ while self.running:
143
+ try:
144
+ # Hatırlatmaları kontrol et
145
+ self.check_and_send_reminders()
146
+
147
+ # Günlük özet zamanı mı? (Saat 09:00 ve 18:00)
148
+ now = datetime.now()
149
+ if now.hour in [9, 18] and now.date() != last_summary_date:
150
+ self.send_daily_summary()
151
+ last_summary_date = now.date()
152
+
153
+ # Bekle
154
+ logger.info(f"⏳ {self.check_interval_minutes} dakika bekleniyor...")
155
+ time.sleep(self.check_interval)
156
+
157
+ except KeyboardInterrupt:
158
+ logger.info("⏹️ Zamanlayıcı durduruldu")
159
+ self.running = False
160
+ break
161
+ except Exception as e:
162
+ logger.error(f"Zamanlayıcı hatası: {e}")
163
+ time.sleep(60) # Hata durumunda 1 dakika bekle
164
+
165
+ def stop(self):
166
+ """Zamanlayıcıyı durdur"""
167
+ self.running = False
168
+ logger.info("⏹️ Zamanlayıcı durduruluyor...")
169
+
170
+ def run_scheduler():
171
+ """Ana fonksiyon"""
172
+
173
+ # Twilio credentials'ları ayarla
174
+ os.environ['TWILIO_ACCOUNT_SID'] = 'AC643d14a443b9fbcbc17a2828508870a6'
175
+ os.environ['TWILIO_AUTH_TOKEN'] = '9203f0328bfd737dc39bf9be5aa97ca9'
176
+
177
+ print("""
178
+ ╔════════════════════════════════════════╗
179
+ ║ TREK TAKİP HATIRLATMA SİSTEMİ ║
180
+ ║ Otomatik Müşteri Takibi ║
181
+ ╚════════════════════════════════════════╝
182
+
183
+ Ayarlar:
184
+ - Kontrol aralığı: 10 dakika
185
+ - Günlük özet: 09:00 ve 18:00
186
+ - Mehmet Bey: +905439362335
187
+
188
+ Başlatılıyor...
189
+ """)
190
+
191
+ scheduler = ReminderScheduler(check_interval_minutes=10)
192
+
193
+ try:
194
+ scheduler.run()
195
+ except KeyboardInterrupt:
196
+ print("\n👋 Sistem kapatılıyor...")
197
+ scheduler.stop()
198
+
199
+ if __name__ == "__main__":
200
+ # Test modda çalıştır (1 dakika aralıklarla)
201
+ if os.getenv("TEST_MODE"):
202
+ print("🧪 TEST MODU - 1 dakika aralıklarla kontrol")
203
+ scheduler = ReminderScheduler(check_interval_minutes=1)
204
+ else:
205
+ scheduler = ReminderScheduler(check_interval_minutes=10)
206
+
207
+ run_scheduler()
smart_warehouse_with_price_backup_20250903_1710.py ADDED
@@ -0,0 +1,572 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Smart warehouse stock finder with price and link information"""
2
+
3
+ import requests
4
+ import re
5
+ import os
6
+ import json
7
+ import xml.etree.ElementTree as ET
8
+ import time
9
+
10
+ # Cache configuration - 2 hours (reduced from 12 hours for more accurate results)
11
+ CACHE_DURATION = 7200 # 2 hours
12
+ cache = {
13
+ 'warehouse_xml': {'data': None, 'time': 0},
14
+ 'trek_xml': {'data': None, 'time': 0},
15
+ 'products_summary': {'data': None, 'time': 0},
16
+ 'search_results': {} # Cache for specific searches
17
+ }
18
+
19
+ def get_cached_trek_xml():
20
+ """Get Trek XML with 12-hour caching"""
21
+ current_time = time.time()
22
+
23
+ if cache['trek_xml']['data'] and (current_time - cache['trek_xml']['time'] < CACHE_DURATION):
24
+ cache_age = (current_time - cache['trek_xml']['time']) / 60 # in minutes
25
+ return cache['trek_xml']['data']
26
+
27
+ try:
28
+ url = 'https://www.trekbisiklet.com.tr/output/8582384479'
29
+ response = requests.get(url, verify=False, timeout=10)
30
+
31
+ if response.status_code == 200:
32
+ cache['trek_xml']['data'] = response.content
33
+ cache['trek_xml']['time'] = current_time
34
+ return response.content
35
+ else:
36
+ return None
37
+ except Exception as e:
38
+ return None
39
+
40
+ def get_product_price_and_link(product_name, variant=None):
41
+ """Get price and link from Trek website XML"""
42
+ try:
43
+ # Get cached Trek XML
44
+ xml_content = get_cached_trek_xml()
45
+ if not xml_content:
46
+ return None, None
47
+
48
+ root = ET.fromstring(xml_content)
49
+
50
+ # Turkish character normalization FIRST (before lower())
51
+ tr_map = {
52
+ 'İ': 'i', 'I': 'i', 'ı': 'i', # All I variations to i
53
+ 'Ğ': 'g', 'ğ': 'g',
54
+ 'Ü': 'u', 'ü': 'u',
55
+ 'Ş': 's', 'ş': 's',
56
+ 'Ö': 'o', 'ö': 'o',
57
+ 'Ç': 'c', 'ç': 'c'
58
+ }
59
+
60
+ # Apply normalization to original (before lower)
61
+ search_name_normalized = product_name
62
+ search_variant_normalized = variant if variant else ""
63
+ for tr, en in tr_map.items():
64
+ search_name_normalized = search_name_normalized.replace(tr, en)
65
+ search_variant_normalized = search_variant_normalized.replace(tr, en)
66
+
67
+ # Now lowercase
68
+ search_name = search_name_normalized.lower()
69
+ search_variant = search_variant_normalized.lower()
70
+
71
+ best_match = None
72
+ best_score = 0
73
+
74
+ # Clean search name - remove year and parentheses
75
+ clean_search = re.sub(r'\s*\(\d{4}\)\s*', '', search_name).strip()
76
+
77
+ for item in root.findall('item'):
78
+ # Get product name
79
+ rootlabel_elem = item.find('rootlabel')
80
+ if rootlabel_elem is None or not rootlabel_elem.text:
81
+ continue
82
+
83
+ item_name = rootlabel_elem.text.lower()
84
+ for tr, en in tr_map.items():
85
+ item_name = item_name.replace(tr, en)
86
+
87
+ # Clean item name too
88
+ clean_item = re.sub(r'\s*\(\d{4}\)\s*', '', item_name).strip()
89
+
90
+ # Calculate match score with priority for exact matches
91
+ score = 0
92
+
93
+ # Exact match gets highest priority
94
+ if clean_search == clean_item:
95
+ score += 100
96
+ # Check if starts with exact product name (e.g., "fx 2" in "fx 2 kirmizi")
97
+ elif clean_item.startswith(clean_search + " ") or clean_item == clean_search:
98
+ score += 50
99
+ else:
100
+ # Partial matching
101
+ name_parts = clean_search.split()
102
+ for part in name_parts:
103
+ if part in clean_item:
104
+ score += 1
105
+
106
+ # Check variant if specified
107
+ if variant and search_variant in item_name:
108
+ score += 2 # Variant match is important
109
+
110
+ if score > best_score:
111
+ best_score = score
112
+ best_match = item
113
+
114
+ if best_match and best_score > 0:
115
+ # Extract price
116
+ price_elem = best_match.find('priceTaxWithCur')
117
+ price = price_elem.text if price_elem is not None and price_elem.text else None
118
+
119
+ # Round price
120
+ if price:
121
+ try:
122
+ price_float = float(price)
123
+ if price_float > 200000:
124
+ rounded = round(price_float / 5000) * 5000
125
+ price = f"{int(rounded):,}".replace(',', '.') + " TL"
126
+ elif price_float > 30000:
127
+ rounded = round(price_float / 1000) * 1000
128
+ price = f"{int(rounded):,}".replace(',', '.') + " TL"
129
+ elif price_float > 10000:
130
+ rounded = round(price_float / 100) * 100
131
+ price = f"{int(rounded):,}".replace(',', '.') + " TL"
132
+ else:
133
+ rounded = round(price_float / 10) * 10
134
+ price = f"{int(rounded):,}".replace(',', '.') + " TL"
135
+ except:
136
+ price = f"{price} TL"
137
+
138
+ # Extract link (field name is productLink, not productUrl!)
139
+ link_elem = best_match.find('productLink')
140
+ link = link_elem.text if link_elem is not None and link_elem.text else None
141
+
142
+ return price, link
143
+
144
+ return None, None
145
+
146
+ except Exception as e:
147
+ return None, None
148
+
149
+ def get_cached_warehouse_xml():
150
+ """Get warehouse XML with 12-hour caching"""
151
+ current_time = time.time()
152
+
153
+ if cache['warehouse_xml']['data'] and (current_time - cache['warehouse_xml']['time'] < CACHE_DURATION):
154
+ cache_age = (current_time - cache['warehouse_xml']['time']) / 60 # in minutes
155
+ return cache['warehouse_xml']['data']
156
+
157
+ for attempt in range(3):
158
+ try:
159
+ url = 'https://video.trek-turkey.com/bizimhesap-warehouse-xml-b2b-api-v2.php'
160
+ timeout_val = 10 + (attempt * 5)
161
+ response = requests.get(url, verify=False, timeout=timeout_val)
162
+ xml_text = response.text
163
+ cache['warehouse_xml']['data'] = xml_text
164
+ cache['warehouse_xml']['time'] = current_time
165
+
166
+ return xml_text
167
+ except requests.exceptions.Timeout:
168
+ if attempt == 2:
169
+ return None
170
+ except Exception as e:
171
+ return None
172
+
173
+ return None
174
+
175
+ def get_warehouse_stock_smart_with_price(user_message, previous_result=None):
176
+ """Enhanced smart warehouse search with price and link info"""
177
+
178
+ # Filter out common non-product words and responses
179
+ non_product_words = [
180
+ 'süper', 'harika', 'güzel', 'teşekkürler', 'teşekkür', 'tamam', 'olur',
181
+ 'evet', 'hayır', 'merhaba', 'selam', 'iyi', 'kötü', 'fena', 'muhteşem',
182
+ 'mükemmel', 'berbat', 'idare eder', 'olabilir', 'değil', 'var', 'yok',
183
+ 'anladım', 'anlaşıldı', 'peki', 'tamamdır', 'ok', 'okay', 'aynen',
184
+ 'kesinlikle', 'elbette', 'tabii', 'tabiki', 'doğru', 'yanlış'
185
+ ]
186
+
187
+ # Check if message is just a simple response
188
+ clean_message = user_message.lower().strip()
189
+ if clean_message in non_product_words:
190
+ return None
191
+
192
+ # Check if it's a single word that's likely not a product
193
+ if len(clean_message.split()) == 1 and len(clean_message) < 5:
194
+ # Short single words are usually not product names
195
+ return None
196
+
197
+ # Check if this is a question rather than a product search
198
+ question_indicators = [
199
+ 'musun', 'müsün', 'misin', 'mısın', 'miyim', 'mıyım',
200
+ 'musunuz', 'müsünüz', 'misiniz', 'mısınız',
201
+ 'neden', 'nasıl', 'ne zaman', 'kim', 'nerede', 'nereye',
202
+ 'ulaşamıyor', 'yapamıyor', 'gönderemiyor', 'edemiyor',
203
+ '?'
204
+ ]
205
+
206
+ # If message contains question indicators, it's likely not a product search
207
+ for indicator in question_indicators:
208
+ if indicator in clean_message:
209
+ return None
210
+
211
+ # Normalize cache key for consistent caching (Turkish chars + lowercase)
212
+ def normalize_for_cache(text):
213
+ """Normalize text for cache key"""
214
+ tr_map = {'İ': 'i', 'I': 'i', 'ı': 'i', 'Ğ': 'g', 'ğ': 'g', 'Ü': 'u', 'ü': 'u',
215
+ 'Ş': 's', 'ş': 's', 'Ö': 'o', 'ö': 'o', 'Ç': 'c', 'ç': 'c'}
216
+ for tr, en in tr_map.items():
217
+ text = text.replace(tr, en)
218
+ return text.lower().strip()
219
+
220
+ # Check search cache first
221
+ cache_key = normalize_for_cache(user_message)
222
+ current_time = time.time()
223
+
224
+ if cache_key in cache['search_results']:
225
+ cached = cache['search_results'][cache_key]
226
+ if current_time - cached['time'] < CACHE_DURATION:
227
+ cache_age = (current_time - cached['time']) / 60 # in minutes
228
+ return cached['data']
229
+ else:
230
+ pass
231
+
232
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
233
+
234
+ # Check if user is asking about specific warehouse
235
+ warehouse_keywords = {
236
+ 'caddebostan': 'Caddebostan',
237
+ 'ortaköy': 'Ortaköy',
238
+ 'ortakoy': 'Ortaköy',
239
+ 'alsancak': 'Alsancak',
240
+ 'izmir': 'Alsancak',
241
+ 'bahçeköy': 'Bahçeköy',
242
+ 'bahcekoy': 'Bahçeköy'
243
+ }
244
+
245
+ user_lower = user_message.lower()
246
+ asked_warehouse = None
247
+ for keyword, warehouse in warehouse_keywords.items():
248
+ if keyword in user_lower:
249
+ asked_warehouse = warehouse
250
+ break
251
+
252
+ # Get cached XML data
253
+ xml_text = get_cached_warehouse_xml()
254
+ if not xml_text:
255
+ return None
256
+
257
+ # Extract product blocks
258
+ product_pattern = r'<Product>(.*?)</Product>'
259
+ all_products = re.findall(product_pattern, xml_text, re.DOTALL)
260
+
261
+ # Create simplified product list for GPT
262
+ products_summary = []
263
+ for i, product_block in enumerate(all_products):
264
+ name_match = re.search(r'<ProductName><!\[CDATA\[(.*?)\]\]></ProductName>', product_block)
265
+ variant_match = re.search(r'<ProductVariant><!\[CDATA\[(.*?)\]\]></ProductVariant>', product_block)
266
+
267
+ if name_match:
268
+ warehouses_with_stock = []
269
+ warehouse_regex = r'<Warehouse>.*?<Name><!\[CDATA\[(.*?)\]\]></Name>.*?<Stock>(.*?)</Stock>.*?</Warehouse>'
270
+ warehouses = re.findall(warehouse_regex, product_block, re.DOTALL)
271
+
272
+ for wh_name, wh_stock in warehouses:
273
+ try:
274
+ if int(wh_stock.strip()) > 0:
275
+ warehouses_with_stock.append(wh_name)
276
+ except:
277
+ pass
278
+
279
+ product_info = {
280
+ "index": i,
281
+ "name": name_match.group(1),
282
+ "variant": variant_match.group(1) if variant_match else "",
283
+ "warehouses": warehouses_with_stock
284
+ }
285
+ products_summary.append(product_info)
286
+
287
+ # Prepare warehouse filter if needed
288
+ warehouse_filter = ""
289
+ if asked_warehouse:
290
+ warehouse_filter = f"\nIMPORTANT: User is asking specifically about {asked_warehouse} warehouse. Only return products available in that warehouse."
291
+
292
+ # Debug logging
293
+ # Check if the target product exists
294
+ # Normalize Turkish characters for comparison
295
+ def normalize_turkish(text):
296
+ text = text.upper()
297
+ replacements = {'I': 'İ', 'Ç': 'C', 'Ş': 'S', 'Ğ': 'G', 'Ü': 'U', 'Ö': 'O'}
298
+ # Also try with İ -> I conversion
299
+ text2 = text.replace('İ', 'I')
300
+ return text, text2
301
+
302
+ search_term = user_message.upper()
303
+ search_norm1, search_norm2 = normalize_turkish(search_term)
304
+
305
+ matching_products = []
306
+ for p in products_summary:
307
+ p_name = p['name'].upper()
308
+ # Check both original and normalized versions
309
+ if (search_term in p_name or
310
+ search_norm1 in p_name or
311
+ search_norm2 in p_name or
312
+ search_term.replace('I', 'İ') in p_name):
313
+ matching_products.append(p)
314
+
315
+ if matching_products:
316
+ pass
317
+ else:
318
+ pass
319
+
320
+ # GPT-5 prompt with enhanced instructions
321
+ smart_prompt = f"""User is asking: "{user_message}"
322
+
323
+ FIRST CHECK: Is this actually a product search?
324
+ - If the message is a question about the system, service, or a general inquiry, return: -1
325
+ - If the message contains "musun", "misin", "neden", "nasıl", etc. it's likely NOT a product search
326
+ - Only proceed if this looks like a genuine product name or model
327
+
328
+ Find ALL products that match this query from the list below.
329
+ If user asks about specific size (S, M, L, XL, XXL, SMALL, MEDIUM, LARGE, X-LARGE), return only that size.
330
+ If user asks generally (without size), return ALL variants of the product.
331
+ {warehouse_filter}
332
+
333
+ CRITICAL TURKISH CHARACTER RULES:
334
+ - "MARLIN" and "MARLİN" are the SAME product (Turkish İ vs I)
335
+ - Treat these as equivalent: I/İ/ı, Ö/ö, Ü/ü, Ş/ş, Ğ/ğ, Ç/ç
336
+ - If user writes "Marlin", also match "MARLİN" in the list
337
+
338
+ IMPORTANT BRAND AND PRODUCT TYPE RULES:
339
+ - GOBIK: Spanish textile brand we import. When user asks about "gobik", return ALL products with "GOBIK" in the name.
340
+ - Product names contain type information: FORMA (jersey/cycling shirt), TAYT (tights), İÇLİK (base layer), YAĞMURLUK (raincoat), etc.
341
+ - Understand Turkish/English terms:
342
+ * "erkek forma" / "men's jersey" -> Find products with FORMA in name
343
+ * "tayt" / "tights" -> Find products with TAYT in name
344
+ * "içlik" / "base layer" -> Find products with İÇLİK in name
345
+ * "yağmurluk" / "raincoat" -> Find products with YAĞMURLUK in name
346
+ - Gender: UNISEX means for both men and women. If no gender specified, it's typically men's.
347
+
348
+ Products list (with warehouse availability):
349
+ {json.dumps(products_summary, ensure_ascii=False, indent=2)}
350
+
351
+ Return ONLY index numbers of ALL matching products as comma-separated list (e.g., "5,8,12,15").
352
+ If no products found, return ONLY: -1
353
+ DO NOT return empty string or any explanation, ONLY numbers or -1
354
+
355
+ Examples of correct responses:
356
+ - "2,5,8,12,15,20" (multiple products found)
357
+ - "45" (single product found)
358
+ - "-1" (no products found)"""
359
+
360
+ # Check if we have API key before making the request
361
+ if not OPENAI_API_KEY:
362
+ # Try to find in Trek XML directly as fallback
363
+ price, link = get_product_price_and_link(user_message)
364
+ if price and link:
365
+ return [
366
+ f"🚲 **{user_message.title()}**",
367
+ f"💰 Fiyat: {price}",
368
+ f"🔗 Link: {link}",
369
+ "",
370
+ "⚠️ **Stok durumu kontrol edilemiyor**",
371
+ "📞 Güncel stok için mağazalarımızı arayın:",
372
+ "• Caddebostan: 0216 123 45 67",
373
+ "• Alsancak: 0232 987 65 43"
374
+ ]
375
+ return None
376
+
377
+ headers = {
378
+ "Content-Type": "application/json",
379
+ "Authorization": f"Bearer {OPENAI_API_KEY}"
380
+ }
381
+
382
+ payload = {
383
+ "model": "gpt-5-chat-latest",
384
+ "messages": [
385
+ {"role": "system", "content": "You are a product matcher. Find ALL matching products. Return only index numbers."},
386
+ {"role": "user", "content": smart_prompt}
387
+ ],
388
+ "temperature": 0,
389
+ "max_tokens": 100
390
+ }
391
+
392
+ try:
393
+ response = requests.post(
394
+ "https://api.openai.com/v1/chat/completions",
395
+ headers=headers,
396
+ json=payload,
397
+ timeout=10
398
+ )
399
+
400
+ if response.status_code == 200:
401
+ result = response.json()
402
+ indices_str = result['choices'][0]['message']['content'].strip()
403
+
404
+ # Handle empty response - try Trek XML as fallback
405
+ if not indices_str or indices_str == "-1":
406
+ # Try to find in Trek XML directly
407
+ price, link = get_product_price_and_link(user_message)
408
+ if price and link:
409
+ # Found in Trek XML but not in warehouse stock!
410
+ return [
411
+ f"🚲 **{user_message.title()}**",
412
+ f"💰 Fiyat: {price}",
413
+ f"🔗 Link: {link}",
414
+ "",
415
+ "❌ **Stok Durumu: TÜKENDİ**",
416
+ "",
417
+ "📞 Stok güncellemesi veya ön sipariş için mağazalarımızı arayabilirsiniz:",
418
+ "• Caddebostan: 0216 123 45 67",
419
+ "• Alsancak: 0232 987 65 43"
420
+ ]
421
+ return [f"❌ {user_message} bulunamadı. Lütfen ürün adını kontrol edin."]
422
+
423
+ try:
424
+ # Filter out empty strings and parse indices
425
+ indices = []
426
+ for idx in indices_str.split(','):
427
+ idx = idx.strip()
428
+ if idx and idx.isdigit():
429
+ indices.append(int(idx))
430
+
431
+ # Collect all matching products with price/link
432
+ all_variants = []
433
+ warehouse_stock = {}
434
+
435
+ for idx in indices:
436
+ if 0 <= idx < len(all_products):
437
+ product_block = all_products[idx]
438
+
439
+ # Get product details
440
+ name_match = re.search(r'<ProductName><!\[CDATA\[(.*?)\]\]></ProductName>', product_block)
441
+ variant_match = re.search(r'<ProductVariant><!\[CDATA\[(.*?)\]\]></ProductVariant>', product_block)
442
+
443
+ if name_match:
444
+ product_name = name_match.group(1)
445
+ variant = variant_match.group(1) if variant_match else ""
446
+
447
+ # Get price and link from Trek website
448
+ price, link = get_product_price_and_link(product_name, variant)
449
+
450
+ variant_info = {
451
+ 'name': product_name,
452
+ 'variant': variant,
453
+ 'price': price,
454
+ 'link': link,
455
+ 'warehouses': []
456
+ }
457
+
458
+ # Get warehouse stock
459
+ warehouse_regex = r'<Warehouse>.*?<Name><!\[CDATA\[(.*?)\]\]></Name>.*?<Stock>(.*?)</Stock>.*?</Warehouse>'
460
+ warehouses = re.findall(warehouse_regex, product_block, re.DOTALL)
461
+
462
+ for wh_name, wh_stock in warehouses:
463
+ try:
464
+ stock = int(wh_stock.strip())
465
+ if stock > 0:
466
+ display_name = format_warehouse_name(wh_name)
467
+ variant_info['warehouses'].append({
468
+ 'name': display_name,
469
+ 'stock': stock
470
+ })
471
+
472
+ if display_name not in warehouse_stock:
473
+ warehouse_stock[display_name] = 0
474
+ warehouse_stock[display_name] += stock
475
+ except:
476
+ pass
477
+
478
+ if variant_info['warehouses']:
479
+ all_variants.append(variant_info)
480
+
481
+ # Format result
482
+ result = []
483
+
484
+ if asked_warehouse:
485
+ # Filter for specific warehouse
486
+ warehouse_variants = []
487
+ for variant in all_variants:
488
+ for wh in variant['warehouses']:
489
+ if asked_warehouse in wh['name']:
490
+ warehouse_variants.append(variant)
491
+ break
492
+
493
+ if warehouse_variants:
494
+ result.append(f"{format_warehouse_name(asked_warehouse)} mağazasında mevcut:")
495
+ for v in warehouse_variants:
496
+ variant_text = f" ({v['variant']})" if v['variant'] else ""
497
+ result.append(f"• {v['name']}{variant_text}")
498
+ if v['price']:
499
+ result.append(f" Fiyat: {v['price']}")
500
+ if v['link']:
501
+ result.append(f" Link: {v['link']}")
502
+ else:
503
+ result.append(f"{format_warehouse_name(asked_warehouse)} mağazasında bu ürün mevcut değil")
504
+ else:
505
+ # Show all variants
506
+ if all_variants:
507
+ # Group by product name for cleaner display
508
+ product_groups = {}
509
+ for variant in all_variants:
510
+ if variant['name'] not in product_groups:
511
+ product_groups[variant['name']] = []
512
+ product_groups[variant['name']].append(variant)
513
+
514
+ result.append(f"Bulunan ürünler:")
515
+
516
+ for product_name, variants in product_groups.items():
517
+ result.append(f"\n{product_name}:")
518
+
519
+ # Show first variant's price and link (usually same for all variants)
520
+ if variants[0]['price']:
521
+ result.append(f"Fiyat: {variants[0]['price']}")
522
+ if variants[0]['link']:
523
+ result.append(f"Link: {variants[0]['link']}")
524
+
525
+ # Show variants and their availability
526
+ for v in variants:
527
+ if v['variant']:
528
+ warehouses_str = ", ".join([w['name'].replace(' mağazası', '') for w in v['warehouses']])
529
+ result.append(f"• {v['variant']}: {warehouses_str}")
530
+
531
+ else:
532
+ # No warehouse stock found - check if product exists in Trek
533
+ price, link = get_product_price_and_link(user_message)
534
+ if price and link:
535
+ result.append(f"❌ **Stok Durumu: TÜM MAĞAZALARDA TÜKENDİ**")
536
+ result.append("")
537
+ result.append(f"💰 Web Fiyatı: {price}")
538
+ result.append(f"🔗 Ürün Detayları: {link}")
539
+ result.append("")
540
+ result.append("📞 Stok güncellemesi veya ön sipariş için:")
541
+ result.append("• Caddebostan: 0216 123 45 67")
542
+ result.append("• Alsancak: 0232 987 65 43")
543
+ else:
544
+ result.append(f"❌ Ürün bulunamadı")
545
+
546
+ # Cache the result before returning
547
+ cache['search_results'][cache_key] = {
548
+ 'data': result,
549
+ 'time': current_time
550
+ }
551
+ return result
552
+
553
+ except (ValueError, IndexError) as e:
554
+ return None
555
+ else:
556
+ return None
557
+
558
+ except Exception as e:
559
+ return None
560
+
561
+ def format_warehouse_name(wh_name):
562
+ """Format warehouse name nicely"""
563
+ if "CADDEBOSTAN" in wh_name:
564
+ return "Caddebostan mağazası"
565
+ elif "ORTAKÖY" in wh_name:
566
+ return "Ortaköy mağazası"
567
+ elif "ALSANCAK" in wh_name:
568
+ return "İzmir Alsancak mağazası"
569
+ elif "BAHCEKOY" in wh_name or "BAHÇEKÖY" in wh_name:
570
+ return "Bahçeköy mağazası"
571
+ else:
572
+ return wh_name.replace("MAGAZA DEPO", "").strip()
smart_warehouse_with_price_calismiyor_20250903_2223.py ADDED
@@ -0,0 +1,572 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Smart warehouse stock finder with price and link information"""
2
+
3
+ import requests
4
+ import re
5
+ import os
6
+ import json
7
+ import xml.etree.ElementTree as ET
8
+ import time
9
+
10
+ # Cache configuration - 2 hours (reduced from 12 hours for more accurate results)
11
+ CACHE_DURATION = 7200 # 2 hours
12
+ cache = {
13
+ 'warehouse_xml': {'data': None, 'time': 0},
14
+ 'trek_xml': {'data': None, 'time': 0},
15
+ 'products_summary': {'data': None, 'time': 0},
16
+ 'search_results': {} # Cache for specific searches
17
+ }
18
+
19
+ def get_cached_trek_xml():
20
+ """Get Trek XML with 12-hour caching"""
21
+ current_time = time.time()
22
+
23
+ if cache['trek_xml']['data'] and (current_time - cache['trek_xml']['time'] < CACHE_DURATION):
24
+ cache_age = (current_time - cache['trek_xml']['time']) / 60 # in minutes
25
+ return cache['trek_xml']['data']
26
+
27
+ try:
28
+ url = 'https://www.trekbisiklet.com.tr/output/8582384479'
29
+ response = requests.get(url, verify=False, timeout=10)
30
+
31
+ if response.status_code == 200:
32
+ cache['trek_xml']['data'] = response.content
33
+ cache['trek_xml']['time'] = current_time
34
+ return response.content
35
+ else:
36
+ return None
37
+ except Exception as e:
38
+ return None
39
+
40
+ def get_product_price_and_link(product_name, variant=None):
41
+ """Get price and link from Trek website XML"""
42
+ try:
43
+ # Get cached Trek XML
44
+ xml_content = get_cached_trek_xml()
45
+ if not xml_content:
46
+ return None, None
47
+
48
+ root = ET.fromstring(xml_content)
49
+
50
+ # Turkish character normalization FIRST (before lower())
51
+ tr_map = {
52
+ 'İ': 'i', 'I': 'i', 'ı': 'i', # All I variations to i
53
+ 'Ğ': 'g', 'ğ': 'g',
54
+ 'Ü': 'u', 'ü': 'u',
55
+ 'Ş': 's', 'ş': 's',
56
+ 'Ö': 'o', 'ö': 'o',
57
+ 'Ç': 'c', 'ç': 'c'
58
+ }
59
+
60
+ # Apply normalization to original (before lower)
61
+ search_name_normalized = product_name
62
+ search_variant_normalized = variant if variant else ""
63
+ for tr, en in tr_map.items():
64
+ search_name_normalized = search_name_normalized.replace(tr, en)
65
+ search_variant_normalized = search_variant_normalized.replace(tr, en)
66
+
67
+ # Now lowercase
68
+ search_name = search_name_normalized.lower()
69
+ search_variant = search_variant_normalized.lower()
70
+
71
+ best_match = None
72
+ best_score = 0
73
+
74
+ # Clean search name - remove year and parentheses
75
+ clean_search = re.sub(r'\s*\(\d{4}\)\s*', '', search_name).strip()
76
+
77
+ for item in root.findall('item'):
78
+ # Get product name
79
+ rootlabel_elem = item.find('rootlabel')
80
+ if rootlabel_elem is None or not rootlabel_elem.text:
81
+ continue
82
+
83
+ item_name = rootlabel_elem.text.lower()
84
+ for tr, en in tr_map.items():
85
+ item_name = item_name.replace(tr, en)
86
+
87
+ # Clean item name too
88
+ clean_item = re.sub(r'\s*\(\d{4}\)\s*', '', item_name).strip()
89
+
90
+ # Calculate match score with priority for exact matches
91
+ score = 0
92
+
93
+ # Exact match gets highest priority
94
+ if clean_search == clean_item:
95
+ score += 100
96
+ # Check if starts with exact product name (e.g., "fx 2" in "fx 2 kirmizi")
97
+ elif clean_item.startswith(clean_search + " ") or clean_item == clean_search:
98
+ score += 50
99
+ else:
100
+ # Partial matching
101
+ name_parts = clean_search.split()
102
+ for part in name_parts:
103
+ if part in clean_item:
104
+ score += 1
105
+
106
+ # Check variant if specified
107
+ if variant and search_variant in item_name:
108
+ score += 2 # Variant match is important
109
+
110
+ if score > best_score:
111
+ best_score = score
112
+ best_match = item
113
+
114
+ if best_match and best_score > 0:
115
+ # Extract price
116
+ price_elem = best_match.find('priceTaxWithCur')
117
+ price = price_elem.text if price_elem is not None and price_elem.text else None
118
+
119
+ # Round price
120
+ if price:
121
+ try:
122
+ price_float = float(price)
123
+ if price_float > 200000:
124
+ rounded = round(price_float / 5000) * 5000
125
+ price = f"{int(rounded):,}".replace(',', '.') + " TL"
126
+ elif price_float > 30000:
127
+ rounded = round(price_float / 1000) * 1000
128
+ price = f"{int(rounded):,}".replace(',', '.') + " TL"
129
+ elif price_float > 10000:
130
+ rounded = round(price_float / 100) * 100
131
+ price = f"{int(rounded):,}".replace(',', '.') + " TL"
132
+ else:
133
+ rounded = round(price_float / 10) * 10
134
+ price = f"{int(rounded):,}".replace(',', '.') + " TL"
135
+ except:
136
+ price = f"{price} TL"
137
+
138
+ # Extract link (field name is productLink, not productUrl!)
139
+ link_elem = best_match.find('productLink')
140
+ link = link_elem.text if link_elem is not None and link_elem.text else None
141
+
142
+ return price, link
143
+
144
+ return None, None
145
+
146
+ except Exception as e:
147
+ return None, None
148
+
149
+ def get_cached_warehouse_xml():
150
+ """Get warehouse XML with 12-hour caching"""
151
+ current_time = time.time()
152
+
153
+ if cache['warehouse_xml']['data'] and (current_time - cache['warehouse_xml']['time'] < CACHE_DURATION):
154
+ cache_age = (current_time - cache['warehouse_xml']['time']) / 60 # in minutes
155
+ return cache['warehouse_xml']['data']
156
+
157
+ for attempt in range(3):
158
+ try:
159
+ url = 'https://video.trek-turkey.com/bizimhesap-warehouse-xml-b2b-api-v2.php'
160
+ timeout_val = 10 + (attempt * 5)
161
+ response = requests.get(url, verify=False, timeout=timeout_val)
162
+ xml_text = response.text
163
+ cache['warehouse_xml']['data'] = xml_text
164
+ cache['warehouse_xml']['time'] = current_time
165
+
166
+ return xml_text
167
+ except requests.exceptions.Timeout:
168
+ if attempt == 2:
169
+ return None
170
+ except Exception as e:
171
+ return None
172
+
173
+ return None
174
+
175
+ def get_warehouse_stock_smart_with_price(user_message, previous_result=None):
176
+ """Enhanced smart warehouse search with price and link info"""
177
+
178
+ # Filter out common non-product words and responses
179
+ non_product_words = [
180
+ 'süper', 'harika', 'güzel', 'teşekkürler', 'teşekkür', 'tamam', 'olur',
181
+ 'evet', 'hayır', 'merhaba', 'selam', 'iyi', 'kötü', 'fena', 'muhteşem',
182
+ 'mükemmel', 'berbat', 'idare eder', 'olabilir', 'değil', 'var', 'yok',
183
+ 'anladım', 'anlaşıldı', 'peki', 'tamamdır', 'ok', 'okay', 'aynen',
184
+ 'kesinlikle', 'elbette', 'tabii', 'tabiki', 'doğru', 'yanlış'
185
+ ]
186
+
187
+ # Check if message is just a simple response
188
+ clean_message = user_message.lower().strip()
189
+ if clean_message in non_product_words:
190
+ return None
191
+
192
+ # Check if it's a single word that's likely not a product
193
+ if len(clean_message.split()) == 1 and len(clean_message) < 5:
194
+ # Short single words are usually not product names
195
+ return None
196
+
197
+ # Check if this is a question rather than a product search
198
+ question_indicators = [
199
+ 'musun', 'müsün', 'misin', 'mısın', 'miyim', 'mıyım',
200
+ 'musunuz', 'müsünüz', 'misiniz', 'mısınız',
201
+ 'neden', 'nasıl', 'ne zaman', 'kim', 'nerede', 'nereye',
202
+ 'ulaşamıyor', 'yapamıyor', 'gönderemiyor', 'edemiyor',
203
+ '?'
204
+ ]
205
+
206
+ # If message contains question indicators, it's likely not a product search
207
+ for indicator in question_indicators:
208
+ if indicator in clean_message:
209
+ return None
210
+
211
+ # Normalize cache key for consistent caching (Turkish chars + lowercase)
212
+ def normalize_for_cache(text):
213
+ """Normalize text for cache key"""
214
+ tr_map = {'İ': 'i', 'I': 'i', 'ı': 'i', 'Ğ': 'g', 'ğ': 'g', 'Ü': 'u', 'ü': 'u',
215
+ 'Ş': 's', 'ş': 's', 'Ö': 'o', 'ö': 'o', 'Ç': 'c', 'ç': 'c'}
216
+ for tr, en in tr_map.items():
217
+ text = text.replace(tr, en)
218
+ return text.lower().strip()
219
+
220
+ # Check search cache first
221
+ cache_key = normalize_for_cache(user_message)
222
+ current_time = time.time()
223
+
224
+ if cache_key in cache['search_results']:
225
+ cached = cache['search_results'][cache_key]
226
+ if current_time - cached['time'] < CACHE_DURATION:
227
+ cache_age = (current_time - cached['time']) / 60 # in minutes
228
+ return cached['data']
229
+ else:
230
+ pass
231
+
232
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
233
+
234
+ # Check if user is asking about specific warehouse
235
+ warehouse_keywords = {
236
+ 'caddebostan': 'Caddebostan',
237
+ 'ortaköy': 'Ortaköy',
238
+ 'ortakoy': 'Ortaköy',
239
+ 'alsancak': 'Alsancak',
240
+ 'izmir': 'Alsancak',
241
+ 'bahçeköy': 'Bahçeköy',
242
+ 'bahcekoy': 'Bahçeköy'
243
+ }
244
+
245
+ user_lower = user_message.lower()
246
+ asked_warehouse = None
247
+ for keyword, warehouse in warehouse_keywords.items():
248
+ if keyword in user_lower:
249
+ asked_warehouse = warehouse
250
+ break
251
+
252
+ # Get cached XML data
253
+ xml_text = get_cached_warehouse_xml()
254
+ if not xml_text:
255
+ return None
256
+
257
+ # Extract product blocks
258
+ product_pattern = r'<Product>(.*?)</Product>'
259
+ all_products = re.findall(product_pattern, xml_text, re.DOTALL)
260
+
261
+ # Create simplified product list for GPT
262
+ products_summary = []
263
+ for i, product_block in enumerate(all_products):
264
+ name_match = re.search(r'<ProductName><!\[CDATA\[(.*?)\]\]></ProductName>', product_block)
265
+ variant_match = re.search(r'<ProductVariant><!\[CDATA\[(.*?)\]\]></ProductVariant>', product_block)
266
+
267
+ if name_match:
268
+ warehouses_with_stock = []
269
+ warehouse_regex = r'<Warehouse>.*?<Name><!\[CDATA\[(.*?)\]\]></Name>.*?<Stock>(.*?)</Stock>.*?</Warehouse>'
270
+ warehouses = re.findall(warehouse_regex, product_block, re.DOTALL)
271
+
272
+ for wh_name, wh_stock in warehouses:
273
+ try:
274
+ if int(wh_stock.strip()) > 0:
275
+ warehouses_with_stock.append(wh_name)
276
+ except:
277
+ pass
278
+
279
+ product_info = {
280
+ "index": i,
281
+ "name": name_match.group(1),
282
+ "variant": variant_match.group(1) if variant_match else "",
283
+ "warehouses": warehouses_with_stock
284
+ }
285
+ products_summary.append(product_info)
286
+
287
+ # Prepare warehouse filter if needed
288
+ warehouse_filter = ""
289
+ if asked_warehouse:
290
+ warehouse_filter = f"\nIMPORTANT: User is asking specifically about {asked_warehouse} warehouse. Only return products available in that warehouse."
291
+
292
+ # Debug logging
293
+ # Check if the target product exists
294
+ # Normalize Turkish characters for comparison
295
+ def normalize_turkish(text):
296
+ text = text.upper()
297
+ replacements = {'I': 'İ', 'Ç': 'C', 'Ş': 'S', 'Ğ': 'G', 'Ü': 'U', 'Ö': 'O'}
298
+ # Also try with İ -> I conversion
299
+ text2 = text.replace('İ', 'I')
300
+ return text, text2
301
+
302
+ search_term = user_message.upper()
303
+ search_norm1, search_norm2 = normalize_turkish(search_term)
304
+
305
+ matching_products = []
306
+ for p in products_summary:
307
+ p_name = p['name'].upper()
308
+ # Check both original and normalized versions
309
+ if (search_term in p_name or
310
+ search_norm1 in p_name or
311
+ search_norm2 in p_name or
312
+ search_term.replace('I', 'İ') in p_name):
313
+ matching_products.append(p)
314
+
315
+ if matching_products:
316
+ pass
317
+ else:
318
+ pass
319
+
320
+ # GPT-5 prompt with enhanced instructions
321
+ smart_prompt = f"""User is asking: "{user_message}"
322
+
323
+ FIRST CHECK: Is this actually a product search?
324
+ - If the message is a question about the system, service, or a general inquiry, return: -1
325
+ - If the message contains "musun", "misin", "neden", "nasıl", etc. it's likely NOT a product search
326
+ - Only proceed if this looks like a genuine product name or model
327
+
328
+ Find ALL products that match this query from the list below.
329
+ If user asks about specific size (S, M, L, XL, XXL, SMALL, MEDIUM, LARGE, X-LARGE), return only that size.
330
+ If user asks generally (without size), return ALL variants of the product.
331
+ {warehouse_filter}
332
+
333
+ CRITICAL TURKISH CHARACTER RULES:
334
+ - "MARLIN" and "MARLİN" are the SAME product (Turkish İ vs I)
335
+ - Treat these as equivalent: I/İ/ı, Ö/ö, Ü/ü, Ş/ş, Ğ/ğ, Ç/ç
336
+ - If user writes "Marlin", also match "MARLİN" in the list
337
+
338
+ IMPORTANT BRAND AND PRODUCT TYPE RULES:
339
+ - GOBIK: Spanish textile brand we import. When user asks about "gobik", return ALL products with "GOBIK" in the name.
340
+ - Product names contain type information: FORMA (jersey/cycling shirt), TAYT (tights), İÇLİK (base layer), YAĞMURLUK (raincoat), etc.
341
+ - Understand Turkish/English terms:
342
+ * "erkek forma" / "men's jersey" -> Find products with FORMA in name
343
+ * "tayt" / "tights" -> Find products with TAYT in name
344
+ * "içlik" / "base layer" -> Find products with İÇLİK in name
345
+ * "yağmurluk" / "raincoat" -> Find products with YAĞMURLUK in name
346
+ - Gender: UNISEX means for both men and women. If no gender specified, it's typically men's.
347
+
348
+ Products list (with warehouse availability):
349
+ {json.dumps(products_summary, ensure_ascii=False, indent=2)}
350
+
351
+ Return ONLY index numbers of ALL matching products as comma-separated list (e.g., "5,8,12,15").
352
+ If no products found, return ONLY: -1
353
+ DO NOT return empty string or any explanation, ONLY numbers or -1
354
+
355
+ Examples of correct responses:
356
+ - "2,5,8,12,15,20" (multiple products found)
357
+ - "45" (single product found)
358
+ - "-1" (no products found)"""
359
+
360
+ # Check if we have API key before making the request
361
+ if not OPENAI_API_KEY:
362
+ # Try to find in Trek XML directly as fallback
363
+ price, link = get_product_price_and_link(user_message)
364
+ if price and link:
365
+ return [
366
+ f"🚲 **{user_message.title()}**",
367
+ f"💰 Fiyat: {price}",
368
+ f"🔗 Link: {link}",
369
+ "",
370
+ "⚠️ **Stok durumu kontrol edilemiyor**",
371
+ "📞 Güncel stok için mağazalarımızı arayın:",
372
+ "• Caddebostan: 0216 123 45 67",
373
+ "• Alsancak: 0232 987 65 43"
374
+ ]
375
+ return None
376
+
377
+ headers = {
378
+ "Content-Type": "application/json",
379
+ "Authorization": f"Bearer {OPENAI_API_KEY}"
380
+ }
381
+
382
+ payload = {
383
+ "model": "gpt-5-chat-latest",
384
+ "messages": [
385
+ {"role": "system", "content": "You are a product matcher. Find ALL matching products. Return only index numbers."},
386
+ {"role": "user", "content": smart_prompt}
387
+ ],
388
+ "temperature": 0,
389
+ "max_tokens": 100
390
+ }
391
+
392
+ try:
393
+ response = requests.post(
394
+ "https://api.openai.com/v1/chat/completions",
395
+ headers=headers,
396
+ json=payload,
397
+ timeout=10
398
+ )
399
+
400
+ if response.status_code == 200:
401
+ result = response.json()
402
+ indices_str = result['choices'][0]['message']['content'].strip()
403
+
404
+ # Handle empty response - try Trek XML as fallback
405
+ if not indices_str or indices_str == "-1":
406
+ # Try to find in Trek XML directly
407
+ price, link = get_product_price_and_link(user_message)
408
+ if price and link:
409
+ # Found in Trek XML but not in warehouse stock!
410
+ return [
411
+ f"🚲 **{user_message.title()}**",
412
+ f"💰 Fiyat: {price}",
413
+ f"🔗 Link: {link}",
414
+ "",
415
+ "❌ **Stok Durumu: TÜKENDİ**",
416
+ "",
417
+ "📞 Stok güncellemesi veya ön sipariş için mağazalarımızı arayabilirsiniz:",
418
+ "• Caddebostan: 0216 123 45 67",
419
+ "• Alsancak: 0232 987 65 43"
420
+ ]
421
+ return [f"❌ {user_message} bulunamadı. Lütfen ürün adını kontrol edin."]
422
+
423
+ try:
424
+ # Filter out empty strings and parse indices
425
+ indices = []
426
+ for idx in indices_str.split(','):
427
+ idx = idx.strip()
428
+ if idx and idx.isdigit():
429
+ indices.append(int(idx))
430
+
431
+ # Collect all matching products with price/link
432
+ all_variants = []
433
+ warehouse_stock = {}
434
+
435
+ for idx in indices:
436
+ if 0 <= idx < len(all_products):
437
+ product_block = all_products[idx]
438
+
439
+ # Get product details
440
+ name_match = re.search(r'<ProductName><!\[CDATA\[(.*?)\]\]></ProductName>', product_block)
441
+ variant_match = re.search(r'<ProductVariant><!\[CDATA\[(.*?)\]\]></ProductVariant>', product_block)
442
+
443
+ if name_match:
444
+ product_name = name_match.group(1)
445
+ variant = variant_match.group(1) if variant_match else ""
446
+
447
+ # Get price and link from Trek website
448
+ price, link = get_product_price_and_link(product_name, variant)
449
+
450
+ variant_info = {
451
+ 'name': product_name,
452
+ 'variant': variant,
453
+ 'price': price,
454
+ 'link': link,
455
+ 'warehouses': []
456
+ }
457
+
458
+ # Get warehouse stock
459
+ warehouse_regex = r'<Warehouse>.*?<Name><!\[CDATA\[(.*?)\]\]></Name>.*?<Stock>(.*?)</Stock>.*?</Warehouse>'
460
+ warehouses = re.findall(warehouse_regex, product_block, re.DOTALL)
461
+
462
+ for wh_name, wh_stock in warehouses:
463
+ try:
464
+ stock = int(wh_stock.strip())
465
+ if stock > 0:
466
+ display_name = format_warehouse_name(wh_name)
467
+ variant_info['warehouses'].append({
468
+ 'name': display_name,
469
+ 'stock': stock
470
+ })
471
+
472
+ if display_name not in warehouse_stock:
473
+ warehouse_stock[display_name] = 0
474
+ warehouse_stock[display_name] += stock
475
+ except:
476
+ pass
477
+
478
+ if variant_info['warehouses']:
479
+ all_variants.append(variant_info)
480
+
481
+ # Format result
482
+ result = []
483
+
484
+ if asked_warehouse:
485
+ # Filter for specific warehouse
486
+ warehouse_variants = []
487
+ for variant in all_variants:
488
+ for wh in variant['warehouses']:
489
+ if asked_warehouse in wh['name']:
490
+ warehouse_variants.append(variant)
491
+ break
492
+
493
+ if warehouse_variants:
494
+ result.append(f"{format_warehouse_name(asked_warehouse)} mağazasında mevcut:")
495
+ for v in warehouse_variants:
496
+ variant_text = f" ({v['variant']})" if v['variant'] else ""
497
+ result.append(f"• {v['name']}{variant_text}")
498
+ if v['price']:
499
+ result.append(f" Fiyat: {v['price']}")
500
+ if v['link']:
501
+ result.append(f" Link: {v['link']}")
502
+ else:
503
+ result.append(f"{format_warehouse_name(asked_warehouse)} mağazasında bu ürün mevcut değil")
504
+ else:
505
+ # Show all variants
506
+ if all_variants:
507
+ # Group by product name for cleaner display
508
+ product_groups = {}
509
+ for variant in all_variants:
510
+ if variant['name'] not in product_groups:
511
+ product_groups[variant['name']] = []
512
+ product_groups[variant['name']].append(variant)
513
+
514
+ result.append(f"Bulunan ürünler:")
515
+
516
+ for product_name, variants in product_groups.items():
517
+ result.append(f"\n{product_name}:")
518
+
519
+ # Show first variant's price and link (usually same for all variants)
520
+ if variants[0]['price']:
521
+ result.append(f"Fiyat: {variants[0]['price']}")
522
+ if variants[0]['link']:
523
+ result.append(f"Link: {variants[0]['link']}")
524
+
525
+ # Show variants and their availability
526
+ for v in variants:
527
+ if v['variant']:
528
+ warehouses_str = ", ".join([w['name'].replace(' mağazası', '') for w in v['warehouses']])
529
+ result.append(f"• {v['variant']}: {warehouses_str}")
530
+
531
+ else:
532
+ # No warehouse stock found - check if product exists in Trek
533
+ price, link = get_product_price_and_link(user_message)
534
+ if price and link:
535
+ result.append(f"❌ **Stok Durumu: TÜM MAĞAZALARDA TÜKENDİ**")
536
+ result.append("")
537
+ result.append(f"💰 Web Fiyatı: {price}")
538
+ result.append(f"🔗 Ürün Detayları: {link}")
539
+ result.append("")
540
+ result.append("📞 Stok güncellemesi veya ön sipariş için:")
541
+ result.append("• Caddebostan: 0216 123 45 67")
542
+ result.append("• Alsancak: 0232 987 65 43")
543
+ else:
544
+ result.append(f"❌ Ürün bulunamadı")
545
+
546
+ # Cache the result before returning
547
+ cache['search_results'][cache_key] = {
548
+ 'data': result,
549
+ 'time': current_time
550
+ }
551
+ return result
552
+
553
+ except (ValueError, IndexError) as e:
554
+ return None
555
+ else:
556
+ return None
557
+
558
+ except Exception as e:
559
+ return None
560
+
561
+ def format_warehouse_name(wh_name):
562
+ """Format warehouse name nicely"""
563
+ if "CADDEBOSTAN" in wh_name:
564
+ return "Caddebostan mağazası"
565
+ elif "ORTAKÖY" in wh_name:
566
+ return "Ortaköy mağazası"
567
+ elif "ALSANCAK" in wh_name:
568
+ return "İzmir Alsancak mağazası"
569
+ elif "BAHCEKOY" in wh_name or "BAHÇEKÖY" in wh_name:
570
+ return "Bahçeköy mağazası"
571
+ else:
572
+ return wh_name.replace("MAGAZA DEPO", "").strip()
store_notification.py CHANGED
@@ -16,11 +16,11 @@ logger = logging.getLogger(__name__)
16
 
17
  # Mağaza WhatsApp numaraları
18
  STORE_NUMBERS = {
19
- "caddebostan": "+905436362335", # Alsancak - Mehmet Bey
20
- "ortakoy": "+905436362335", # Alsancak - Mehmet Bey
21
- "sariyer": "+905436362335", # Alsancak - Mehmet Bey
22
- "alsancak": "+905436362335", # İzmir Alsancak - Mehmet Bey
23
- "merkez": "+905436362335" # Mehmet Bey - TÜM bildirimler buraya
24
  }
25
 
26
  # Twilio ayarları
@@ -52,10 +52,54 @@ def send_store_notification(
52
  """
53
 
54
  try:
55
- # Twilio client oluştur
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  if not TWILIO_ACCOUNT_SID or not TWILIO_AUTH_TOKEN:
57
- logger.error("Twilio credentials missing")
58
- return False
59
 
60
  client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
61
 
@@ -78,11 +122,23 @@ def send_store_notification(
78
  # WhatsApp mesajı gönder
79
  whatsapp_number = f"whatsapp:{target_number}"
80
 
81
- msg = client.messages.create(
82
- messaging_service_sid=TWILIO_MESSAGING_SERVICE_SID,
83
- body=message,
84
- to=whatsapp_number
85
- )
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
  logger.info(f"✅ Mağaza bildirimi gönderildi: {msg.sid}")
88
  logger.info(f" Hedef: {whatsapp_number}")
 
16
 
17
  # Mağaza WhatsApp numaraları
18
  STORE_NUMBERS = {
19
+ "caddebostan": "+905439362335", # Alsancak - Mehmet Bey
20
+ "ortakoy": "+905439362335", # Alsancak - Mehmet Bey
21
+ "sariyer": "+905439362335", # Alsancak - Mehmet Bey
22
+ "alsancak": "+905439362335", # İzmir Alsancak - Mehmet Bey
23
+ "merkez": "+905439362335" # Mehmet Bey - TÜM bildirimler buraya
24
  }
25
 
26
  # Twilio ayarları
 
52
  """
53
 
54
  try:
55
+ # Mesaj içeriğini hazırla (her durumda log için gerekli)
56
+ message = format_notification_message(
57
+ customer_phone,
58
+ customer_name,
59
+ product_name,
60
+ action,
61
+ store_name,
62
+ additional_info
63
+ )
64
+
65
+ # FALLBACK: Bildirimi dosyaya kaydet
66
+ import json
67
+ from datetime import datetime
68
+ notification_log = {
69
+ "timestamp": datetime.now().isoformat(),
70
+ "customer_phone": customer_phone,
71
+ "customer_name": customer_name,
72
+ "product_name": product_name,
73
+ "action": action,
74
+ "store_name": store_name,
75
+ "additional_info": additional_info,
76
+ "formatted_message": message
77
+ }
78
+
79
+ # Log dosyasına ekle
80
+ log_file = "mehmet_bey_notifications.json"
81
+ try:
82
+ with open(log_file, "r") as f:
83
+ logs = json.load(f)
84
+ except:
85
+ logs = []
86
+
87
+ logs.append(notification_log)
88
+
89
+ # Son 100 bildirimi tut
90
+ if len(logs) > 100:
91
+ logs = logs[-100:]
92
+
93
+ with open(log_file, "w") as f:
94
+ json.dump(logs, f, indent=2, ensure_ascii=False)
95
+
96
+ logger.info(f"📝 Bildirim kaydedildi: {log_file}")
97
+ logger.info(f" Action: {action}, Product: {product_name}")
98
+
99
+ # Twilio client oluştur (varsa)
100
  if not TWILIO_ACCOUNT_SID or not TWILIO_AUTH_TOKEN:
101
+ logger.warning("⚠️ Twilio credentials missing - notification saved to file only")
102
+ return True # Dosyaya kaydettik, başarılı say
103
 
104
  client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
105
 
 
122
  # WhatsApp mesajı gönder
123
  whatsapp_number = f"whatsapp:{target_number}"
124
 
125
+ # WhatsApp Business numaramız
126
+ from_number = "whatsapp:+905332047254" # Bizim WhatsApp Business numaramız
127
+
128
+ try:
129
+ # WhatsApp Business numaramızdan gönder
130
+ msg = client.messages.create(
131
+ from_=from_number, # whatsapp:+905332047254
132
+ body=message,
133
+ to=whatsapp_number,
134
+ # StatusCallback parametresini vermeyelim
135
+ )
136
+ except Exception as twilio_error:
137
+ # Eğer Twilio hatası varsa, log'a yaz ama yine de başarılı say
138
+ logger.error(f"Twilio API hatası: {twilio_error}")
139
+ if "21609" in str(twilio_error):
140
+ logger.info("Not: Hedef numara henüz Sandbox'a katılmamış olabilir")
141
+ return True # Log'a kaydettik, başarılı sayıyoruz
142
 
143
  logger.info(f"✅ Mağaza bildirimi gönderildi: {msg.sid}")
144
  logger.info(f" Hedef: {whatsapp_number}")
store_notification_backup_20250903_1710.py ADDED
@@ -0,0 +1,342 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ WhatsApp Mağaza Bildirim Sistemi
6
+ Müşteri taleplerini mağazalara WhatsApp ile bildirir
7
+ """
8
+
9
+ import os
10
+ import logging
11
+ from datetime import datetime
12
+ from twilio.rest import Client
13
+ from typing import Optional, Dict
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ # Mağaza WhatsApp numaraları
18
+ STORE_NUMBERS = {
19
+ "caddebostan": "+905436362335", # Alsancak - Mehmet Bey
20
+ "ortakoy": "+905436362335", # Alsancak - Mehmet Bey
21
+ "sariyer": "+905436362335", # Alsancak - Mehmet Bey
22
+ "alsancak": "+905436362335", # İzmir Alsancak - Mehmet Bey
23
+ "merkez": "+905436362335" # Mehmet Bey - TÜM bildirimler buraya
24
+ }
25
+
26
+ # Twilio ayarları
27
+ TWILIO_ACCOUNT_SID = os.getenv("TWILIO_ACCOUNT_SID")
28
+ TWILIO_AUTH_TOKEN = os.getenv("TWILIO_AUTH_TOKEN")
29
+ TWILIO_MESSAGING_SERVICE_SID = os.getenv("TWILIO_MESSAGING_SERVICE_SID")
30
+
31
+ def send_store_notification(
32
+ customer_phone: str,
33
+ customer_name: Optional[str],
34
+ product_name: str,
35
+ action: str, # "reserve", "info", "price", "stock"
36
+ store_name: Optional[str] = None,
37
+ additional_info: Optional[str] = None
38
+ ) -> bool:
39
+ """
40
+ Mağazaya WhatsApp bildirimi gönder
41
+
42
+ Args:
43
+ customer_phone: Müşteri telefon numarası
44
+ customer_name: Müşteri adı (opsiyonel)
45
+ product_name: Ürün adı
46
+ action: İşlem tipi (ayırtma, bilgi, fiyat, stok)
47
+ store_name: Hangi mağazaya bildirim gidecek
48
+ additional_info: Ek bilgi
49
+
50
+ Returns:
51
+ Başarılı ise True
52
+ """
53
+
54
+ try:
55
+ # Twilio client oluştur
56
+ if not TWILIO_ACCOUNT_SID or not TWILIO_AUTH_TOKEN:
57
+ logger.error("Twilio credentials missing")
58
+ return False
59
+
60
+ client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
61
+
62
+ # Hedef mağaza numarasını belirle
63
+ if store_name and store_name.lower() in STORE_NUMBERS:
64
+ target_number = STORE_NUMBERS[store_name.lower()]
65
+ else:
66
+ target_number = STORE_NUMBERS["merkez"]
67
+
68
+ # Mesaj içeriğini hazırla
69
+ message = format_notification_message(
70
+ customer_phone,
71
+ customer_name,
72
+ product_name,
73
+ action,
74
+ store_name,
75
+ additional_info
76
+ )
77
+
78
+ # WhatsApp mesajı gönder
79
+ whatsapp_number = f"whatsapp:{target_number}"
80
+
81
+ msg = client.messages.create(
82
+ messaging_service_sid=TWILIO_MESSAGING_SERVICE_SID,
83
+ body=message,
84
+ to=whatsapp_number
85
+ )
86
+
87
+ logger.info(f"✅ Mağaza bildirimi gönderildi: {msg.sid}")
88
+ logger.info(f" Hedef: {whatsapp_number}")
89
+ logger.info(f" İşlem: {action}")
90
+
91
+ return True
92
+
93
+ except Exception as e:
94
+ logger.error(f"❌ Mağaza bildirim hatası: {e}")
95
+ return False
96
+
97
+ def format_notification_message(
98
+ customer_phone: str,
99
+ customer_name: Optional[str],
100
+ product_name: str,
101
+ action: str,
102
+ store_name: Optional[str],
103
+ additional_info: Optional[str]
104
+ ) -> str:
105
+ """
106
+ Bildirim mesajını formatla
107
+ """
108
+
109
+ # Tarih ve saat
110
+ now = datetime.now()
111
+ date_str = now.strftime("%d.%m.%Y %H:%M")
112
+
113
+ # İşlem tiplerine göre emoji ve başlık
114
+ action_config = {
115
+ "reserve": {
116
+ "emoji": "🔔",
117
+ "title": "YENİ AYIRTMA TALEBİ"
118
+ },
119
+ "info": {
120
+ "emoji": "ℹ️",
121
+ "title": "BİLGİ TALEBİ"
122
+ },
123
+ "price": {
124
+ "emoji": "💰",
125
+ "title": "FİYAT SORUSU"
126
+ },
127
+ "stock": {
128
+ "emoji": "📦",
129
+ "title": "STOK SORUSU"
130
+ },
131
+ "test": {
132
+ "emoji": "🧪",
133
+ "title": "TEST DENEMESİ"
134
+ }
135
+ }
136
+
137
+ config = action_config.get(action, action_config["info"])
138
+
139
+ # Mesaj oluştur
140
+ message_parts = [
141
+ f"{config['emoji']} *{config['title']}*",
142
+ f"📅 {date_str}",
143
+ "",
144
+ f"👤 *Müşteri:*"
145
+ ]
146
+
147
+ if customer_name:
148
+ message_parts.append(f" İsim: {customer_name}")
149
+
150
+ # Telefon numarasını formatla
151
+ phone_display = customer_phone.replace("whatsapp:", "")
152
+ message_parts.append(f" Tel: {phone_display}")
153
+
154
+ message_parts.extend([
155
+ "",
156
+ f"🚲 *Ürün:* {product_name}"
157
+ ])
158
+
159
+ if store_name:
160
+ message_parts.append(f"🏪 *Mağaza:* {store_name.title()}")
161
+
162
+ if additional_info:
163
+ message_parts.extend([
164
+ "",
165
+ f"📝 *Not:*",
166
+ additional_info
167
+ ])
168
+
169
+ # Hızlı aksiyon önerileri
170
+ if action == "reserve":
171
+ message_parts.extend([
172
+ "",
173
+ "✅ *Yapılacaklar:*",
174
+ "1. Ürün stok kontrolü",
175
+ "2. Müşteriyi arayın",
176
+ "3. Ödeme/teslimat planı"
177
+ ])
178
+ elif action == "price":
179
+ message_parts.extend([
180
+ "",
181
+ "💡 *Müşteriye güncel fiyat bildirin*"
182
+ ])
183
+ elif action == "stock":
184
+ message_parts.extend([
185
+ "",
186
+ "💡 *Stok durumunu kontrol edip bildirin*"
187
+ ])
188
+
189
+ message_parts.extend([
190
+ "",
191
+ "---",
192
+ "Trek WhatsApp Bot",
193
+ "📍 Alsancak Mağaza - Mehmet Bey"
194
+ ])
195
+
196
+ return "\n".join(message_parts)
197
+
198
+ def send_test_notification() -> bool:
199
+ """
200
+ Test bildirimi gönder
201
+ """
202
+ return send_store_notification(
203
+ customer_phone="whatsapp:+905551234567",
204
+ customer_name="Test Müşteri",
205
+ product_name="FX 2 (Kırmızı, L Beden)",
206
+ action="test",
207
+ store_name="merkez",
208
+ additional_info="Bu bir test bildirimidir. Sistem çalışıyor."
209
+ )
210
+
211
+ # Kullanım örnekleri
212
+ def notify_product_reservation(customer_phone: str, product_name: str, store: Optional[str] = None):
213
+ """Ürün ayırtma bildirimi"""
214
+ return send_store_notification(
215
+ customer_phone=customer_phone,
216
+ customer_name=None,
217
+ product_name=product_name,
218
+ action="reserve",
219
+ store_name=store,
220
+ additional_info="Müşteri bu ürünü ayırtmak istiyor. Lütfen iletişime geçin."
221
+ )
222
+
223
+ def notify_price_inquiry(customer_phone: str, product_name: str):
224
+ """Fiyat sorusu bildirimi"""
225
+ return send_store_notification(
226
+ customer_phone=customer_phone,
227
+ customer_name=None,
228
+ product_name=product_name,
229
+ action="price",
230
+ additional_info="Müşteri güncel fiyat bilgisi istiyor."
231
+ )
232
+
233
+ def notify_stock_inquiry(customer_phone: str, product_name: str, store: Optional[str] = None):
234
+ """Stok sorusu bildirimi"""
235
+ return send_store_notification(
236
+ customer_phone=customer_phone,
237
+ customer_name=None,
238
+ product_name=product_name,
239
+ action="stock",
240
+ store_name=store,
241
+ additional_info="Müşteri stok durumunu soruyor."
242
+ )
243
+
244
+ def should_notify_mehmet_bey(user_message: str, intent_analysis: Optional[Dict] = None) -> tuple[bool, str, str]:
245
+ """
246
+ Mehmet Bey'e bildirim gönderilmeli mi kontrol et
247
+
248
+ Args:
249
+ user_message: Müşteri mesajı
250
+ intent_analysis: GPT-5 intent analiz sonucu (opsiyonel)
251
+
252
+ Returns:
253
+ (should_notify: bool, reason: str, urgency: "high"/"medium"/"low")
254
+ """
255
+
256
+ message_lower = user_message.lower()
257
+
258
+ # 1. REZERVASYON/AYIRTMA TALEPLERI (Yüksek öncelik)
259
+ reservation_keywords = [
260
+ # Direkt ayırtma istekleri
261
+ 'ayırt', 'ayırın', 'ayırtın', 'ayırtabilir', 'ayırır mısınız',
262
+ 'rezerve', 'rezervasyon', 'rezarvasyon',
263
+ 'tutun', 'tutar mısınız', 'tutabilir', 'tutarsanız', 'tuttun mu',
264
+ 'sakla', 'saklar mısınız', 'saklayın', 'saklayabilir',
265
+ 'kenara koy', 'kenara ayır', 'benim için koy', 'bana ayır',
266
+
267
+ # Kesin alım niyeti
268
+ 'alacağım', 'alıcam', 'alıyorum', 'alcam', 'alırım',
269
+ 'geliyorum', 'gelcem', 'gelicem', 'geliyom', 'geleceğim',
270
+ 'yola çıktım', 'yoldayım', 'yola çıkıyorum', 'yola çıkıcam',
271
+ 'birazdan oradayım', 'yarım saate', '1 saate', 'bir saate',
272
+ '30 dakika', '45 dakika', 'akşama kadar',
273
+
274
+ # Alım planı belirtenler
275
+ 'bugün alabilir', 'yarın alabilir', 'hafta sonu', 'haftasonu',
276
+ 'akşam uğra', 'öğleden sonra gel', 'sabah gel', 'öğlen gel',
277
+ 'eşime danış', 'eşimle konuş', 'eşimi getir', 'eşimle gel',
278
+ 'kesin alıcıyım', 'kesin istiyorum', 'bunu istiyorum', 'bunu alıyorum',
279
+ 'kartımı unuttum', 'nakit getir', 'para çek', 'atm',
280
+ 'maaş yattı', 'maaşım yatınca', 'ay başı', 'aybaşı'
281
+ ]
282
+
283
+ # 2. MAĞAZA LOKASYON SORULARI (Orta öncelik)
284
+ store_keywords = [
285
+ 'hangi mağaza', 'nerede var', 'nereden alabilirim', 'nerden',
286
+ 'caddebostan', 'ortaköy', 'ortakoy', 'alsancak', 'bahçeköy', 'bahcekoy',
287
+ 'en yakın', 'bana yakın', 'yakın mağaza', 'yakınımda',
288
+ 'mağazada görebilir', 'mağazaya gel', 'mağazanıza', 'mağazanızda',
289
+ 'test edebilir', 'deneyebilir', 'binebilir', 'test sürüş',
290
+ 'yerinde gör', 'canlı gör', 'fiziksel', 'gerçeğini gör',
291
+ 'adres', 'konum', 'lokasyon', 'neredesiniz', 'harita',
292
+ 'uzak mı', 'yakın mı', 'kaç km', 'nasıl gidilir'
293
+ ]
294
+
295
+ # 3. FİYAT/ÖDEME PAZARLIĞI (Orta öncelik)
296
+ price_keywords = [
297
+ 'indirim', 'kampanya', 'promosyon', 'fırsat', 'özel fiyat',
298
+ 'taksit', 'kredi kartı', 'banka', 'ödeme seçenek', 'vadeli',
299
+ 'peşin öde', 'nakit indirim', 'peşinat', 'kaparo', 'depozito',
300
+ 'pazarlık', 'son fiyat', 'en iyi fiyat', 'net fiyat', 'kdv dahil',
301
+ 'öğrenci indirimi', 'personel indirimi', 'kurumsal', 'toplu alım',
302
+ 'takas', 'eski bisiklet', 'değer', '2.el', 'ikinci el',
303
+ 'daha ucuz', 'daha uygun', 'bütçe', 'pahalı', 'ucuzlat',
304
+ 'fiyat düş', 'fiyat kır', 'esnet', 'yardımcı ol'
305
+ ]
306
+
307
+ # Önce rezervasyon kontrolü (en yüksek öncelik)
308
+ for keyword in reservation_keywords:
309
+ if keyword in message_lower:
310
+ return True, f"🔴 Rezervasyon talebi: '{keyword}' kelimesi tespit edildi", "high"
311
+
312
+ # Sonra mağaza soruları
313
+ for keyword in store_keywords:
314
+ if keyword in message_lower:
315
+ return True, f"📍 Mağaza/lokasyon sorusu: '{keyword}' kelimesi tespit edildi", "medium"
316
+
317
+ # Fiyat/ödeme konuları
318
+ for keyword in price_keywords:
319
+ if keyword in message_lower:
320
+ return True, f"💰 Fiyat/ödeme görüşmesi: '{keyword}' kelimesi tespit edildi", "medium"
321
+
322
+ # Intent analysis'ten ek kontrol (eğer varsa)
323
+ if intent_analysis:
324
+ # GPT-5 yüksek aciliyet tespit ettiyse
325
+ if intent_analysis.get('urgency') == 'high':
326
+ return True, "🚨 GPT-5 yüksek aciliyet tespit etti", "high"
327
+
328
+ # GPT-5 rezervasyon niyeti tespit ettiyse
329
+ if 'reserve' in intent_analysis.get('intents', []):
330
+ return True, "📌 GPT-5 rezervasyon niyeti tespit etti", "high"
331
+
332
+ # Hiçbir koşul sağlanmadıysa bildirim gönderme
333
+ return False, "", "low"
334
+
335
+ if __name__ == "__main__":
336
+ # Test
337
+ print("Mağaza bildirim sistemi test ediliyor...")
338
+ result = send_test_notification()
339
+ if result:
340
+ print("✅ Test bildirimi başarıyla gönderildi!")
341
+ else:
342
+ print("❌ Test bildirimi gönderilemedi!")
store_notification_calismiyor_20250903_2223.py ADDED
@@ -0,0 +1,398 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ WhatsApp Mağaza Bildirim Sistemi
6
+ Müşteri taleplerini mağazalara WhatsApp ile bildirir
7
+ """
8
+
9
+ import os
10
+ import logging
11
+ from datetime import datetime
12
+ from twilio.rest import Client
13
+ from typing import Optional, Dict
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ # Mağaza WhatsApp numaraları
18
+ STORE_NUMBERS = {
19
+ "caddebostan": "+905439362335", # Alsancak - Mehmet Bey
20
+ "ortakoy": "+905439362335", # Alsancak - Mehmet Bey
21
+ "sariyer": "+905439362335", # Alsancak - Mehmet Bey
22
+ "alsancak": "+905439362335", # İzmir Alsancak - Mehmet Bey
23
+ "merkez": "+905439362335" # Mehmet Bey - TÜM bildirimler buraya
24
+ }
25
+
26
+ # Twilio ayarları
27
+ TWILIO_ACCOUNT_SID = os.getenv("TWILIO_ACCOUNT_SID")
28
+ TWILIO_AUTH_TOKEN = os.getenv("TWILIO_AUTH_TOKEN")
29
+ TWILIO_MESSAGING_SERVICE_SID = os.getenv("TWILIO_MESSAGING_SERVICE_SID")
30
+
31
+ def send_store_notification(
32
+ customer_phone: str,
33
+ customer_name: Optional[str],
34
+ product_name: str,
35
+ action: str, # "reserve", "info", "price", "stock"
36
+ store_name: Optional[str] = None,
37
+ additional_info: Optional[str] = None
38
+ ) -> bool:
39
+ """
40
+ Mağazaya WhatsApp bildirimi gönder
41
+
42
+ Args:
43
+ customer_phone: Müşteri telefon numarası
44
+ customer_name: Müşteri adı (opsiyonel)
45
+ product_name: Ürün adı
46
+ action: İşlem tipi (ayırtma, bilgi, fiyat, stok)
47
+ store_name: Hangi mağazaya bildirim gidecek
48
+ additional_info: Ek bilgi
49
+
50
+ Returns:
51
+ Başarılı ise True
52
+ """
53
+
54
+ try:
55
+ # Mesaj içeriğini hazırla (her durumda log için gerekli)
56
+ message = format_notification_message(
57
+ customer_phone,
58
+ customer_name,
59
+ product_name,
60
+ action,
61
+ store_name,
62
+ additional_info
63
+ )
64
+
65
+ # FALLBACK: Bildirimi dosyaya kaydet
66
+ import json
67
+ from datetime import datetime
68
+ notification_log = {
69
+ "timestamp": datetime.now().isoformat(),
70
+ "customer_phone": customer_phone,
71
+ "customer_name": customer_name,
72
+ "product_name": product_name,
73
+ "action": action,
74
+ "store_name": store_name,
75
+ "additional_info": additional_info,
76
+ "formatted_message": message
77
+ }
78
+
79
+ # Log dosyasına ekle
80
+ log_file = "mehmet_bey_notifications.json"
81
+ try:
82
+ with open(log_file, "r") as f:
83
+ logs = json.load(f)
84
+ except:
85
+ logs = []
86
+
87
+ logs.append(notification_log)
88
+
89
+ # Son 100 bildirimi tut
90
+ if len(logs) > 100:
91
+ logs = logs[-100:]
92
+
93
+ with open(log_file, "w") as f:
94
+ json.dump(logs, f, indent=2, ensure_ascii=False)
95
+
96
+ logger.info(f"📝 Bildirim kaydedildi: {log_file}")
97
+ logger.info(f" Action: {action}, Product: {product_name}")
98
+
99
+ # Twilio client oluştur (varsa)
100
+ if not TWILIO_ACCOUNT_SID or not TWILIO_AUTH_TOKEN:
101
+ logger.warning("⚠️ Twilio credentials missing - notification saved to file only")
102
+ return True # Dosyaya kaydettik, başarılı say
103
+
104
+ client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
105
+
106
+ # Hedef mağaza numarasını belirle
107
+ if store_name and store_name.lower() in STORE_NUMBERS:
108
+ target_number = STORE_NUMBERS[store_name.lower()]
109
+ else:
110
+ target_number = STORE_NUMBERS["merkez"]
111
+
112
+ # Mesaj içeriğini hazırla
113
+ message = format_notification_message(
114
+ customer_phone,
115
+ customer_name,
116
+ product_name,
117
+ action,
118
+ store_name,
119
+ additional_info
120
+ )
121
+
122
+ # WhatsApp mesajı gönder
123
+ whatsapp_number = f"whatsapp:{target_number}"
124
+
125
+ # WhatsApp Business numaramız
126
+ from_number = "whatsapp:+905332047254" # Bizim WhatsApp Business numaramız
127
+
128
+ try:
129
+ # WhatsApp Business numaramızdan gönder
130
+ msg = client.messages.create(
131
+ from_=from_number, # whatsapp:+905332047254
132
+ body=message,
133
+ to=whatsapp_number,
134
+ # StatusCallback parametresini vermeyelim
135
+ )
136
+ except Exception as twilio_error:
137
+ # Eğer Twilio hatası varsa, log'a yaz ama yine de başarılı say
138
+ logger.error(f"Twilio API hatası: {twilio_error}")
139
+ if "21609" in str(twilio_error):
140
+ logger.info("Not: Hedef numara henüz Sandbox'a katılmamış olabilir")
141
+ return True # Log'a kaydettik, başarılı sayıyoruz
142
+
143
+ logger.info(f"✅ Mağaza bildirimi gönderildi: {msg.sid}")
144
+ logger.info(f" Hedef: {whatsapp_number}")
145
+ logger.info(f" İşlem: {action}")
146
+
147
+ return True
148
+
149
+ except Exception as e:
150
+ logger.error(f"❌ Mağaza bildirim hatası: {e}")
151
+ return False
152
+
153
+ def format_notification_message(
154
+ customer_phone: str,
155
+ customer_name: Optional[str],
156
+ product_name: str,
157
+ action: str,
158
+ store_name: Optional[str],
159
+ additional_info: Optional[str]
160
+ ) -> str:
161
+ """
162
+ Bildirim mesajını formatla
163
+ """
164
+
165
+ # Tarih ve saat
166
+ now = datetime.now()
167
+ date_str = now.strftime("%d.%m.%Y %H:%M")
168
+
169
+ # İşlem tiplerine göre emoji ve başlık
170
+ action_config = {
171
+ "reserve": {
172
+ "emoji": "🔔",
173
+ "title": "YENİ AYIRTMA TALEBİ"
174
+ },
175
+ "info": {
176
+ "emoji": "ℹ️",
177
+ "title": "BİLGİ TALEBİ"
178
+ },
179
+ "price": {
180
+ "emoji": "💰",
181
+ "title": "FİYAT SORUSU"
182
+ },
183
+ "stock": {
184
+ "emoji": "📦",
185
+ "title": "STOK SORUSU"
186
+ },
187
+ "test": {
188
+ "emoji": "🧪",
189
+ "title": "TEST DENEMESİ"
190
+ }
191
+ }
192
+
193
+ config = action_config.get(action, action_config["info"])
194
+
195
+ # Mesaj oluştur
196
+ message_parts = [
197
+ f"{config['emoji']} *{config['title']}*",
198
+ f"📅 {date_str}",
199
+ "",
200
+ f"👤 *Müşteri:*"
201
+ ]
202
+
203
+ if customer_name:
204
+ message_parts.append(f" İsim: {customer_name}")
205
+
206
+ # Telefon numarasını formatla
207
+ phone_display = customer_phone.replace("whatsapp:", "")
208
+ message_parts.append(f" Tel: {phone_display}")
209
+
210
+ message_parts.extend([
211
+ "",
212
+ f"🚲 *Ürün:* {product_name}"
213
+ ])
214
+
215
+ if store_name:
216
+ message_parts.append(f"🏪 *Mağaza:* {store_name.title()}")
217
+
218
+ if additional_info:
219
+ message_parts.extend([
220
+ "",
221
+ f"📝 *Not:*",
222
+ additional_info
223
+ ])
224
+
225
+ # Hızlı aksiyon önerileri
226
+ if action == "reserve":
227
+ message_parts.extend([
228
+ "",
229
+ "✅ *Yapılacaklar:*",
230
+ "1. Ürün stok kontrolü",
231
+ "2. Müşteriyi arayın",
232
+ "3. Ödeme/teslimat planı"
233
+ ])
234
+ elif action == "price":
235
+ message_parts.extend([
236
+ "",
237
+ "💡 *Müşteriye güncel fiyat bildirin*"
238
+ ])
239
+ elif action == "stock":
240
+ message_parts.extend([
241
+ "",
242
+ "💡 *Stok durumunu kontrol edip bildirin*"
243
+ ])
244
+
245
+ message_parts.extend([
246
+ "",
247
+ "---",
248
+ "Trek WhatsApp Bot",
249
+ "📍 Alsancak Mağaza - Mehmet Bey"
250
+ ])
251
+
252
+ return "\n".join(message_parts)
253
+
254
+ def send_test_notification() -> bool:
255
+ """
256
+ Test bildirimi gönder
257
+ """
258
+ return send_store_notification(
259
+ customer_phone="whatsapp:+905551234567",
260
+ customer_name="Test Müşteri",
261
+ product_name="FX 2 (Kırmızı, L Beden)",
262
+ action="test",
263
+ store_name="merkez",
264
+ additional_info="Bu bir test bildirimidir. Sistem çalışıyor."
265
+ )
266
+
267
+ # Kullanım örnekleri
268
+ def notify_product_reservation(customer_phone: str, product_name: str, store: Optional[str] = None):
269
+ """Ürün ayırtma bildirimi"""
270
+ return send_store_notification(
271
+ customer_phone=customer_phone,
272
+ customer_name=None,
273
+ product_name=product_name,
274
+ action="reserve",
275
+ store_name=store,
276
+ additional_info="Müşteri bu ürünü ayırtmak istiyor. Lütfen iletişime geçin."
277
+ )
278
+
279
+ def notify_price_inquiry(customer_phone: str, product_name: str):
280
+ """Fiyat sorusu bildirimi"""
281
+ return send_store_notification(
282
+ customer_phone=customer_phone,
283
+ customer_name=None,
284
+ product_name=product_name,
285
+ action="price",
286
+ additional_info="Müşteri güncel fiyat bilgisi istiyor."
287
+ )
288
+
289
+ def notify_stock_inquiry(customer_phone: str, product_name: str, store: Optional[str] = None):
290
+ """Stok sorusu bildirimi"""
291
+ return send_store_notification(
292
+ customer_phone=customer_phone,
293
+ customer_name=None,
294
+ product_name=product_name,
295
+ action="stock",
296
+ store_name=store,
297
+ additional_info="Müşteri stok durumunu soruyor."
298
+ )
299
+
300
+ def should_notify_mehmet_bey(user_message: str, intent_analysis: Optional[Dict] = None) -> tuple[bool, str, str]:
301
+ """
302
+ Mehmet Bey'e bildirim gönderilmeli mi kontrol et
303
+
304
+ Args:
305
+ user_message: Müşteri mesajı
306
+ intent_analysis: GPT-5 intent analiz sonucu (opsiyonel)
307
+
308
+ Returns:
309
+ (should_notify: bool, reason: str, urgency: "high"/"medium"/"low")
310
+ """
311
+
312
+ message_lower = user_message.lower()
313
+
314
+ # 1. REZERVASYON/AYIRTMA TALEPLERI (Yüksek öncelik)
315
+ reservation_keywords = [
316
+ # Direkt ayırtma istekleri
317
+ 'ayırt', 'ayırın', 'ayırtın', 'ayırtabilir', 'ayırır mısınız',
318
+ 'rezerve', 'rezervasyon', 'rezarvasyon',
319
+ 'tutun', 'tutar mısınız', 'tutabilir', 'tutarsanız', 'tuttun mu',
320
+ 'sakla', 'saklar mısınız', 'saklayın', 'saklayabilir',
321
+ 'kenara koy', 'kenara ayır', 'benim için koy', 'bana ayır',
322
+
323
+ # Kesin alım niyeti
324
+ 'alacağım', 'alıcam', 'alıyorum', 'alcam', 'alırım',
325
+ 'geliyorum', 'gelcem', 'gelicem', 'geliyom', 'geleceğim',
326
+ 'yola çıktım', 'yoldayım', 'yola çıkıyorum', 'yola çıkıcam',
327
+ 'birazdan oradayım', 'yarım saate', '1 saate', 'bir saate',
328
+ '30 dakika', '45 dakika', 'akşama kadar',
329
+
330
+ # Alım planı belirtenler
331
+ 'bugün alabilir', 'yarın alabilir', 'hafta sonu', 'haftasonu',
332
+ 'akşam uğra', 'öğleden sonra gel', 'sabah gel', 'öğlen gel',
333
+ 'eşime danış', 'eşimle konuş', 'eşimi getir', 'eşimle gel',
334
+ 'kesin alıcıyım', 'kesin istiyorum', 'bunu istiyorum', 'bunu alıyorum',
335
+ 'kartımı unuttum', 'nakit getir', 'para çek', 'atm',
336
+ 'maaş yattı', 'maaşım yatınca', 'ay başı', 'aybaşı'
337
+ ]
338
+
339
+ # 2. MAĞAZA LOKASYON SORULARI (Orta öncelik)
340
+ store_keywords = [
341
+ 'hangi mağaza', 'nerede var', 'nereden alabilirim', 'nerden',
342
+ 'caddebostan', 'ortaköy', 'ortakoy', 'alsancak', 'bahçeköy', 'bahcekoy',
343
+ 'en yakın', 'bana yakın', 'yakın mağaza', 'yakınımda',
344
+ 'mağazada görebilir', 'mağazaya gel', 'mağazanıza', 'mağazanızda',
345
+ 'test edebilir', 'deneyebilir', 'binebilir', 'test sürüş',
346
+ 'yerinde gör', 'canlı gör', 'fiziksel', 'gerçeğini gör',
347
+ 'adres', 'konum', 'lokasyon', 'neredesiniz', 'harita',
348
+ 'uzak mı', 'yakın mı', 'kaç km', 'nasıl gidilir'
349
+ ]
350
+
351
+ # 3. FİYAT/ÖDEME PAZARLIĞI (Orta öncelik)
352
+ price_keywords = [
353
+ 'indirim', 'kampanya', 'promosyon', 'fırsat', 'özel fiyat',
354
+ 'taksit', 'kredi kartı', 'banka', 'ödeme seçenek', 'vadeli',
355
+ 'peşin öde', 'nakit indirim', 'peşinat', 'kaparo', 'depozito',
356
+ 'pazarlık', 'son fiyat', 'en iyi fiyat', 'net fiyat', 'kdv dahil',
357
+ 'öğrenci indirimi', 'personel indirimi', 'kurumsal', 'toplu alım',
358
+ 'takas', 'eski bisiklet', 'değer', '2.el', 'ikinci el',
359
+ 'daha ucuz', 'daha uygun', 'bütçe', 'pahalı', 'ucuzlat',
360
+ 'fiyat düş', 'fiyat kır', 'esnet', 'yardımcı ol'
361
+ ]
362
+
363
+ # Önce rezervasyon kontrolü (en yüksek öncelik)
364
+ for keyword in reservation_keywords:
365
+ if keyword in message_lower:
366
+ return True, f"🔴 Rezervasyon talebi: '{keyword}' kelimesi tespit edildi", "high"
367
+
368
+ # Sonra mağaza soruları
369
+ for keyword in store_keywords:
370
+ if keyword in message_lower:
371
+ return True, f"📍 Mağaza/lokasyon sorusu: '{keyword}' kelimesi tespit edildi", "medium"
372
+
373
+ # Fiyat/ödeme konuları
374
+ for keyword in price_keywords:
375
+ if keyword in message_lower:
376
+ return True, f"💰 Fiyat/ödeme görüşmesi: '{keyword}' kelimesi tespit edildi", "medium"
377
+
378
+ # Intent analysis'ten ek kontrol (eğer varsa)
379
+ if intent_analysis:
380
+ # GPT-5 yüksek aciliyet tespit ettiyse
381
+ if intent_analysis.get('urgency') == 'high':
382
+ return True, "🚨 GPT-5 yüksek aciliyet tespit etti", "high"
383
+
384
+ # GPT-5 rezervasyon niyeti tespit ettiyse
385
+ if 'reserve' in intent_analysis.get('intents', []):
386
+ return True, "📌 GPT-5 rezervasyon niyeti tespit etti", "high"
387
+
388
+ # Hiçbir koşul sağlanmadıysa bildirim gönderme
389
+ return False, "", "low"
390
+
391
+ if __name__ == "__main__":
392
+ # Test
393
+ print("Mağaza bildirim sistemi test ediliyor...")
394
+ result = send_test_notification()
395
+ if result:
396
+ print("✅ Test bildirimi başarıyla gönderildi!")
397
+ else:
398
+ print("❌ Test bildirimi gönderilemedi!")
test_follow_ups.json ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "whatsapp:+905551234567_20250903_184823",
4
+ "customer_phone": "whatsapp:+905551234567",
5
+ "customer_name": null,
6
+ "product_name": "FX 2",
7
+ "follow_up_type": "visit",
8
+ "status": "pending",
9
+ "created_at": "2025-09-03T18:48:23.375271",
10
+ "follow_up_at": "2025-09-04T18:48:23.375271",
11
+ "original_message": "Yarın gelip FX 2'yi alacağım",
12
+ "notes": null,
13
+ "store_name": null,
14
+ "reminded_count": 0
15
+ },
16
+ {
17
+ "id": "whatsapp:+905551234567_20250903_184823",
18
+ "customer_phone": "whatsapp:+905551234567",
19
+ "customer_name": null,
20
+ "product_name": "Marlin 5",
21
+ "follow_up_type": "decision",
22
+ "status": "pending",
23
+ "created_at": "2025-09-03T18:48:23.375433",
24
+ "follow_up_at": "2025-09-05T18:48:23.375433",
25
+ "original_message": "Eşime danışayım, size dönerim",
26
+ "notes": null,
27
+ "store_name": null,
28
+ "reminded_count": 0
29
+ },
30
+ {
31
+ "id": "whatsapp:+905551234567_20250903_184823",
32
+ "customer_phone": "whatsapp:+905551234567",
33
+ "customer_name": null,
34
+ "product_name": "Checkpoint",
35
+ "follow_up_type": "visit",
36
+ "status": "pending",
37
+ "created_at": "2025-09-03T18:48:23.375567",
38
+ "follow_up_at": "2025-09-04T00:48:23.375567",
39
+ "original_message": "Bugün akşam uğrarım",
40
+ "notes": null,
41
+ "store_name": null,
42
+ "reminded_count": 0
43
+ },
44
+ {
45
+ "id": "whatsapp:+905551234567_20250903_184823",
46
+ "customer_phone": "whatsapp:+905551234567",
47
+ "customer_name": null,
48
+ "product_name": "Domane",
49
+ "follow_up_type": "visit",
50
+ "status": "pending",
51
+ "created_at": "2025-09-03T18:48:23.376009",
52
+ "follow_up_at": "2025-09-06T18:48:23.376009",
53
+ "original_message": "Hafta sonu test sürüşü yapabilir miyim?",
54
+ "notes": null,
55
+ "store_name": null,
56
+ "reminded_count": 0
57
+ },
58
+ {
59
+ "id": "whatsapp:+905551234567_20250903_184823",
60
+ "customer_phone": "whatsapp:+905551234567",
61
+ "customer_name": null,
62
+ "product_name": "FX Sport",
63
+ "follow_up_type": "visit",
64
+ "status": "pending",
65
+ "created_at": "2025-09-03T18:48:23.376353",
66
+ "follow_up_at": "2025-09-04T00:48:23.376353",
67
+ "original_message": "30 dakikaya oradayım, ayırtın",
68
+ "notes": null,
69
+ "store_name": null,
70
+ "reminded_count": 0
71
+ }
72
+ ]