|
|
""" |
|
|
CRM (Customer Relationship Management) System |
|
|
Müşteri tanıma, segmentasyon ve kişiselleştirilmiş yanıtlar |
|
|
""" |
|
|
import json |
|
|
import os |
|
|
from datetime import datetime, timedelta |
|
|
from typing import Dict, List, Optional, Tuple |
|
|
import re |
|
|
|
|
|
class CustomerManager: |
|
|
def __init__(self, db_path: str = "customers.json"): |
|
|
self.db_path = db_path |
|
|
self.customers = self._load_database() |
|
|
|
|
|
def _load_database(self) -> Dict: |
|
|
"""Müşteri veritabanını yükle""" |
|
|
if os.path.exists(self.db_path): |
|
|
try: |
|
|
with open(self.db_path, 'r', encoding='utf-8') as f: |
|
|
return json.load(f) |
|
|
except: |
|
|
return {} |
|
|
return {} |
|
|
|
|
|
def _save_database(self): |
|
|
"""Veritabanını kaydet""" |
|
|
with open(self.db_path, 'w', encoding='utf-8') as f: |
|
|
json.dump(self.customers, f, ensure_ascii=False, indent=2) |
|
|
|
|
|
def _extract_name_from_message(self, message: str) -> Optional[str]: |
|
|
"""Mesajdan isim çıkarmaya çalış""" |
|
|
|
|
|
patterns = [ |
|
|
r"ben[im]?\s+ad[ıi]m?\s+(\w+)", |
|
|
r"ben\s+(\w+)", |
|
|
r"ad[ıi]m\s+(\w+)", |
|
|
r"(\w+)\s+diye", |
|
|
] |
|
|
|
|
|
message_lower = message.lower() |
|
|
for pattern in patterns: |
|
|
match = re.search(pattern, message_lower) |
|
|
if match: |
|
|
name = match.group(1) |
|
|
|
|
|
return name.capitalize() |
|
|
return None |
|
|
|
|
|
def _detect_interests(self, message: str) -> List[str]: |
|
|
"""Mesajdan ilgi alanlarını çıkar""" |
|
|
interests = [] |
|
|
message_lower = message.lower() |
|
|
|
|
|
|
|
|
if any(word in message_lower for word in ["dağ", "mountain", "mtb"]): |
|
|
interests.append("mountain_bike") |
|
|
if any(word in message_lower for word in ["yol", "road", "yarış"]): |
|
|
interests.append("road_bike") |
|
|
if any(word in message_lower for word in ["şehir", "city", "urban"]): |
|
|
interests.append("city_bike") |
|
|
|
|
|
|
|
|
if "trek" in message_lower: |
|
|
interests.append("trek") |
|
|
if "marlin" in message_lower: |
|
|
interests.append("marlin_series") |
|
|
if "fx" in message_lower: |
|
|
interests.append("fx_series") |
|
|
if "domane" in message_lower: |
|
|
interests.append("domane_series") |
|
|
if "madone" in message_lower: |
|
|
interests.append("madone_series") |
|
|
|
|
|
|
|
|
if any(word in message_lower for word in ["elektrik", "e-bike", "ebike"]): |
|
|
interests.append("e_bike") |
|
|
if any(word in message_lower for word in ["karbon", "carbon"]): |
|
|
interests.append("carbon") |
|
|
if any(word in message_lower for word in ["kadın", "women", "wsd"]): |
|
|
interests.append("women_specific") |
|
|
|
|
|
|
|
|
if any(word in message_lower for word in ["ucuz", "uygun fiyat", "ekonomik"]): |
|
|
interests.append("budget_friendly") |
|
|
if any(word in message_lower for word in ["premium", "high-end", "üst segment"]): |
|
|
interests.append("premium") |
|
|
|
|
|
return interests |
|
|
|
|
|
def _calculate_segment(self, customer: Dict) -> str: |
|
|
"""Müşteri segmentini hesapla""" |
|
|
total_purchases = len(customer.get("purchases", [])) |
|
|
total_spent = sum(p.get("price", 0) for p in customer.get("purchases", [])) |
|
|
total_queries = customer.get("total_queries", 0) |
|
|
|
|
|
|
|
|
last_interaction = datetime.fromisoformat(customer.get("last_interaction", datetime.now().isoformat())) |
|
|
days_since_last = (datetime.now() - last_interaction).days |
|
|
|
|
|
|
|
|
if total_purchases >= 3 or total_spent >= 50000: |
|
|
return "VIP" |
|
|
|
|
|
|
|
|
if total_queries >= 5 and total_purchases == 0: |
|
|
return "Potansiyel" |
|
|
|
|
|
|
|
|
if days_since_last > 30 and total_purchases > 0: |
|
|
return "Kayıp Riski" |
|
|
|
|
|
|
|
|
first_seen = datetime.fromisoformat(customer.get("first_seen", datetime.now().isoformat())) |
|
|
if (datetime.now() - first_seen).days <= 7: |
|
|
return "Yeni" |
|
|
|
|
|
|
|
|
if days_since_last <= 7: |
|
|
return "Aktif" |
|
|
|
|
|
|
|
|
return "Standart" |
|
|
|
|
|
def get_or_create_customer(self, phone: str, name: Optional[str] = None) -> Dict: |
|
|
"""Müşteriyi getir veya yeni oluştur""" |
|
|
if phone in self.customers: |
|
|
|
|
|
customer = self.customers[phone] |
|
|
customer["last_interaction"] = datetime.now().isoformat() |
|
|
|
|
|
|
|
|
if name and not customer.get("name"): |
|
|
customer["name"] = name |
|
|
|
|
|
|
|
|
customer["segment"] = self._calculate_segment(customer) |
|
|
|
|
|
else: |
|
|
|
|
|
customer = { |
|
|
"phone": phone, |
|
|
"name": name, |
|
|
"first_seen": datetime.now().isoformat(), |
|
|
"last_interaction": datetime.now().isoformat(), |
|
|
"total_queries": 0, |
|
|
"purchases": [], |
|
|
"interests": [], |
|
|
"searched_products": [], |
|
|
"segment": "Yeni", |
|
|
"notes": [], |
|
|
"preferred_contact_time": None |
|
|
} |
|
|
self.customers[phone] = customer |
|
|
|
|
|
self._save_database() |
|
|
return customer |
|
|
|
|
|
def update_interaction(self, phone: str, message: str, product_searched: Optional[str] = None) -> Dict: |
|
|
"""Müşteri etkileşimini güncelle""" |
|
|
customer = self.get_or_create_customer(phone) |
|
|
|
|
|
|
|
|
customer["total_queries"] = customer.get("total_queries", 0) + 1 |
|
|
|
|
|
|
|
|
if not customer.get("name"): |
|
|
extracted_name = self._extract_name_from_message(message) |
|
|
if extracted_name: |
|
|
customer["name"] = extracted_name |
|
|
|
|
|
|
|
|
new_interests = self._detect_interests(message) |
|
|
existing_interests = set(customer.get("interests", [])) |
|
|
customer["interests"] = list(existing_interests.union(set(new_interests))) |
|
|
|
|
|
|
|
|
if product_searched: |
|
|
searched_products = customer.get("searched_products", []) |
|
|
|
|
|
searched_products.append({ |
|
|
"product": product_searched, |
|
|
"date": datetime.now().isoformat() |
|
|
}) |
|
|
customer["searched_products"] = searched_products[-10:] |
|
|
|
|
|
|
|
|
hour = datetime.now().hour |
|
|
if 6 <= hour < 12: |
|
|
time_pref = "morning" |
|
|
elif 12 <= hour < 18: |
|
|
time_pref = "afternoon" |
|
|
else: |
|
|
time_pref = "evening" |
|
|
|
|
|
|
|
|
if not customer.get("preferred_contact_time"): |
|
|
customer["preferred_contact_time"] = time_pref |
|
|
|
|
|
|
|
|
customer["segment"] = self._calculate_segment(customer) |
|
|
|
|
|
|
|
|
customer["last_interaction"] = datetime.now().isoformat() |
|
|
|
|
|
self._save_database() |
|
|
return customer |
|
|
|
|
|
def add_purchase(self, phone: str, product: str, price: float) -> Dict: |
|
|
"""Satın alma kaydı ekle""" |
|
|
customer = self.get_or_create_customer(phone) |
|
|
|
|
|
purchase = { |
|
|
"product": product, |
|
|
"price": price, |
|
|
"date": datetime.now().isoformat() |
|
|
} |
|
|
|
|
|
customer.setdefault("purchases", []).append(purchase) |
|
|
customer["segment"] = self._calculate_segment(customer) |
|
|
|
|
|
self._save_database() |
|
|
return customer |
|
|
|
|
|
def get_customer_context(self, phone: str) -> Dict: |
|
|
"""Müşteri bağlamını getir""" |
|
|
customer = self.customers.get(phone, {}) |
|
|
|
|
|
if not customer: |
|
|
return { |
|
|
"is_new": True, |
|
|
"greeting": "Merhaba! Trek bisikletleri hakkında size nasıl yardımcı olabilirim?" |
|
|
} |
|
|
|
|
|
context = { |
|
|
"is_new": False, |
|
|
"name": customer.get("name"), |
|
|
"segment": customer.get("segment", "Standart"), |
|
|
"total_queries": customer.get("total_queries", 0), |
|
|
"interests": customer.get("interests", []), |
|
|
"last_product": None, |
|
|
"days_since_last": 0, |
|
|
"greeting": "" |
|
|
} |
|
|
|
|
|
|
|
|
searched_products = customer.get("searched_products", []) |
|
|
if searched_products: |
|
|
context["last_product"] = searched_products[-1]["product"] |
|
|
last_search_date = datetime.fromisoformat(searched_products[-1]["date"]) |
|
|
context["days_since_last"] = (datetime.now() - last_search_date).days |
|
|
|
|
|
|
|
|
context["greeting"] = self._generate_greeting(customer, context) |
|
|
|
|
|
return context |
|
|
|
|
|
def _generate_greeting(self, customer: Dict, context: Dict) -> str: |
|
|
"""Kişiselleştirilmiş selamlama oluştur""" |
|
|
segment = customer.get("segment", "Standart") |
|
|
name = customer.get("name", "") |
|
|
|
|
|
|
|
|
hour = datetime.now().hour |
|
|
if 6 <= hour < 12: |
|
|
time_greeting = "Günaydın" |
|
|
elif 12 <= hour < 18: |
|
|
time_greeting = "İyi günler" |
|
|
else: |
|
|
time_greeting = "İyi akşamlar" |
|
|
|
|
|
|
|
|
if segment == "VIP": |
|
|
if name: |
|
|
greeting = f"{time_greeting} {name} Bey/Hanım! Değerli müşterimiz olarak size özel VIP avantajlarımız mevcut." |
|
|
else: |
|
|
greeting = f"{time_greeting}! Değerli müşterimize özel VIP avantajlarımız mevcut." |
|
|
|
|
|
elif segment == "Yeni": |
|
|
greeting = f"{time_greeting}! Trek bisikletleri dünyasına hoş geldiniz! Size nasıl yardımcı olabilirim?" |
|
|
|
|
|
elif segment == "Potansiyel": |
|
|
if context.get("last_product"): |
|
|
greeting = f"{time_greeting}! Daha önce sorduğunuz {context['last_product']} veya başka bir konuda size yardımcı olabilir miyim?" |
|
|
else: |
|
|
greeting = f"{time_greeting}! Bisiklet arayışınızda size nasıl yardımcı olabilirim?" |
|
|
|
|
|
elif segment == "Kayıp Riski": |
|
|
if name: |
|
|
greeting = f"{time_greeting} {name} Bey/Hanım! Sizi özledik! Size özel geri dönüş kampanyamız var." |
|
|
else: |
|
|
greeting = f"{time_greeting}! Sizi tekrar aramızda görmek güzel! Size özel fırsatlarımız var." |
|
|
|
|
|
elif segment == "Aktif": |
|
|
if name: |
|
|
greeting = f"{time_greeting} {name} Bey/Hanım! Tekrar hoş geldiniz!" |
|
|
else: |
|
|
greeting = f"{time_greeting}! Tekrar hoş geldiniz!" |
|
|
|
|
|
|
|
|
if context.get("last_product") and context.get("days_since_last", 99) <= 3: |
|
|
greeting += f" {context['last_product']} hakkında bilgi almak ister misiniz?" |
|
|
|
|
|
else: |
|
|
if name: |
|
|
greeting = f"{time_greeting} {name} Bey/Hanım! Size nasıl yardımcı olabilirim?" |
|
|
else: |
|
|
greeting = f"{time_greeting}! Size nasıl yardımcı olabilirim?" |
|
|
|
|
|
return greeting |
|
|
|
|
|
def get_analytics(self) -> Dict: |
|
|
"""Analitik özet döndür""" |
|
|
total_customers = len(self.customers) |
|
|
segments = {} |
|
|
total_purchases = 0 |
|
|
total_revenue = 0 |
|
|
active_today = 0 |
|
|
|
|
|
today = datetime.now().date() |
|
|
|
|
|
for customer in self.customers.values(): |
|
|
|
|
|
segment = customer.get("segment", "Standart") |
|
|
segments[segment] = segments.get(segment, 0) + 1 |
|
|
|
|
|
|
|
|
purchases = customer.get("purchases", []) |
|
|
total_purchases += len(purchases) |
|
|
total_revenue += sum(p.get("price", 0) for p in purchases) |
|
|
|
|
|
|
|
|
last_interaction = datetime.fromisoformat(customer.get("last_interaction", "2020-01-01")) |
|
|
if last_interaction.date() == today: |
|
|
active_today += 1 |
|
|
|
|
|
return { |
|
|
"total_customers": total_customers, |
|
|
"segments": segments, |
|
|
"total_purchases": total_purchases, |
|
|
"total_revenue": total_revenue, |
|
|
"active_today": active_today, |
|
|
"average_purchase_value": total_revenue / total_purchases if total_purchases > 0 else 0 |
|
|
} |
|
|
|
|
|
def get_customer_list(self, segment: Optional[str] = None) -> List[Dict]: |
|
|
"""Müşteri listesini getir""" |
|
|
customers = [] |
|
|
|
|
|
for phone, customer in self.customers.items(): |
|
|
if segment and customer.get("segment") != segment: |
|
|
continue |
|
|
|
|
|
customers.append({ |
|
|
"phone": phone, |
|
|
"name": customer.get("name", "Bilinmiyor"), |
|
|
"segment": customer.get("segment", "Standart"), |
|
|
"total_queries": customer.get("total_queries", 0), |
|
|
"total_purchases": len(customer.get("purchases", [])), |
|
|
"last_interaction": customer.get("last_interaction"), |
|
|
"interests": customer.get("interests", []) |
|
|
}) |
|
|
|
|
|
|
|
|
customers.sort(key=lambda x: x["last_interaction"], reverse=True) |
|
|
return customers |
|
|
|
|
|
def should_ask_for_name(self, phone: str, message: str) -> Tuple[bool, Optional[str]]: |
|
|
"""İsim sorulmalı mı kontrol et""" |
|
|
customer = self.customers.get(phone, {}) |
|
|
|
|
|
|
|
|
if customer.get("name"): |
|
|
return False, None |
|
|
|
|
|
queries = customer.get("total_queries", 0) |
|
|
message_lower = message.lower() |
|
|
|
|
|
|
|
|
reservation_keywords = ["rezerv", "ayırt", "satın al", "alabilir miyim", "almak istiyorum", |
|
|
"sipariş", "kargola", "gönder", "paket", "teslimat"] |
|
|
if any(keyword in message_lower for keyword in reservation_keywords): |
|
|
return True, "📝 Rezervasyon için adınızı alabilir miyim?" |
|
|
|
|
|
|
|
|
store_visit_keywords = ["mağazaya gel", "mağazaya uğra", "görmeye gel", "bakmaya gel", |
|
|
"test sürüşü", "denemek istiyorum"] |
|
|
if any(keyword in message_lower for keyword in store_visit_keywords): |
|
|
return True, "🏪 Mağazada size daha iyi yardımcı olabilmemiz için adınızı öğrenebilir miyim?" |
|
|
|
|
|
|
|
|
phone_keywords = ["ara", "telefon", "görüş", "konuş", "iletişim"] |
|
|
if any(keyword in message_lower for keyword in phone_keywords): |
|
|
return True, "📞 Sizi arayabilmemiz için adınızı paylaşır mısınız?" |
|
|
|
|
|
|
|
|
payment_keywords = ["taksit", "ödeme", "kredi kartı", "havale", "eft", "nakit", "peşin"] |
|
|
if any(keyword in message_lower for keyword in payment_keywords): |
|
|
return True, "💳 Size özel ödeme seçenekleri sunabilmem için adınızı öğrenebilir miyim?" |
|
|
|
|
|
|
|
|
if queries == 2: |
|
|
|
|
|
greeting_only = ["merhaba", "selam", "günaydın", "iyi günler", "hey", "sa"] |
|
|
if not any(message_lower.strip() == greeting for greeting in greeting_only): |
|
|
return True, "Bu arada, size daha iyi hizmet verebilmem için adınızı öğrenebilir miyim? 😊" |
|
|
|
|
|
|
|
|
if queries >= 5: |
|
|
return True, "✨ Sizin için özel tekliflerimiz olabilir. Adınızı paylaşır mısınız?" |
|
|
|
|
|
return False, None |
|
|
|
|
|
def extract_name_from_response(self, message: str) -> Optional[str]: |
|
|
"""İsim sorulduktan sonra gelen yanıttan isim çıkar""" |
|
|
message_lower = message.lower().strip() |
|
|
|
|
|
|
|
|
if len(message_lower.split()) == 1: |
|
|
|
|
|
common_words = ["evet", "hayır", "tamam", "olur", "peki", "teşekkür", "teşekkürler", |
|
|
"merhaba", "selam", "günaydın", "güle", "hoşçakal", "görüşürüz", |
|
|
"sa", "as", "slm", "mrb", "ok", "okay", "yes", "no", "hi", "hello", "bye"] |
|
|
if message_lower not in common_words: |
|
|
|
|
|
name = message.capitalize() |
|
|
|
|
|
if len(name) >= 2 and name.isalpha(): |
|
|
return name |
|
|
|
|
|
|
|
|
patterns = [ |
|
|
r"(?:benim\s+)?ad[ıi]m?\s+(\w+)", |
|
|
r"ben\s+(\w+)", |
|
|
r"(\w+)\s+diye\s+hitap", |
|
|
r"bana\s+(\w+)\s+diyebilirsiniz", |
|
|
r"ismim\s+(\w+)", |
|
|
r"(\w+),?\s*teşekkür", |
|
|
] |
|
|
|
|
|
for pattern in patterns: |
|
|
import re |
|
|
match = re.search(pattern, message_lower) |
|
|
if match: |
|
|
name = match.group(1).capitalize() |
|
|
if len(name) >= 2: |
|
|
return name |
|
|
|
|
|
|
|
|
two_words = message.strip().split() |
|
|
if len(two_words) == 2: |
|
|
|
|
|
if two_words[0][0].isupper() and two_words[1][0].isupper(): |
|
|
return two_words[0] |
|
|
|
|
|
return None |