|
|
|
|
|
""" |
|
|
WhatsApp Pasif Profil Sistemi |
|
|
Kullanıcıya soru sormadan sohbet analizi ile profil oluşturur |
|
|
""" |
|
|
|
|
|
import re |
|
|
import json |
|
|
import os |
|
|
from datetime import datetime, timedelta |
|
|
from typing import Dict, List, Optional, Any |
|
|
|
|
|
class WhatsAppPassiveProfiler: |
|
|
"""Sohbet analizi ile kullanıcı profili çıkarır""" |
|
|
|
|
|
def __init__(self): |
|
|
self.profiles_file = "user_profiles.json" |
|
|
self.profiles = self.load_profiles() |
|
|
|
|
|
|
|
|
self.budget_patterns = [ |
|
|
r'bütçem?\s*(\d+)[\s-]*(\d+)?\s*k?\s*(bin|bin tl|tl)?', |
|
|
r'(\d+)[\s-]*(\d+)?\s*k?\s*(bin|bin tl|tl)?\s*bütçe', |
|
|
r'(\d+)[\s-]*(\d+)?\s*k?\s*(bin|bin tl|tl)?\s*arasında', |
|
|
r'maksimum\s*(\d+)\s*k?\s*(bin|bin tl|tl)?', |
|
|
r'en fazla\s*(\d+)\s*k?\s*(bin|bin tl|tl)?' |
|
|
] |
|
|
|
|
|
|
|
|
self.category_keywords = { |
|
|
"dağ_bisikleti": ["dağ", "dag", "offroad", "patika", "doğa", "orman", "marlin", "fuel", "procaliber"], |
|
|
"yol_bisikleti": ["yol", "asfalt", "hız", "yarış", "triathlon", "émonda", "madone", "domane"], |
|
|
"şehir_bisikleti": ["şehir", "kent", "günlük", "işe gidip gelme", "fx", "ds", "verve"], |
|
|
"elektrikli": ["elektrikli", "electric", "e-bike", "ebike", "batarya", "powerfly", "rail"], |
|
|
"gravel": ["gravel", "çakıl", "macera", "touring", "checkpoint"] |
|
|
} |
|
|
|
|
|
|
|
|
self.usage_patterns = { |
|
|
"spor": ["spor", "antrenman", "kondisyon", "fitness", "egzersiz", "yarış"], |
|
|
"günlük": ["işe gitmek", "günlük", "şehir içi", "ulaşım", "market", "alışveriş"], |
|
|
"hobi": ["hobi", "eğlence", "gezinti", "keyif", "hafta sonu", "macera"], |
|
|
"profesyonel": ["profesyonel", "yarış", "müsabaka", "antrenör", "ciddi"] |
|
|
} |
|
|
|
|
|
|
|
|
self.size_patterns = { |
|
|
"boy": r'boyum\s*(\d+)\s*cm?|(\d+)\s*cm?\s*boy', |
|
|
"kilo": r'kilom\s*(\d+)\s*kg?|(\d+)\s*kg?\s*kilo', |
|
|
"beden": r'beden[im]?\s*([xsl]+)|([xsl]+)\s*beden' |
|
|
} |
|
|
|
|
|
def load_profiles(self) -> Dict: |
|
|
"""Mevcut profilleri yükle""" |
|
|
if os.path.exists(self.profiles_file): |
|
|
try: |
|
|
with open(self.profiles_file, 'r', encoding='utf-8') as f: |
|
|
return json.load(f) |
|
|
except Exception as e: |
|
|
print(f"Profil yükleme hatası: {e}") |
|
|
return {} |
|
|
|
|
|
def save_profiles(self): |
|
|
"""Profilleri kaydet""" |
|
|
try: |
|
|
with open(self.profiles_file, 'w', encoding='utf-8') as f: |
|
|
json.dump(self.profiles, f, ensure_ascii=False, indent=2, default=str) |
|
|
except Exception as e: |
|
|
print(f"Profil kaydetme hatası: {e}") |
|
|
|
|
|
def get_or_create_profile(self, phone_number: str) -> Dict: |
|
|
"""Profil getir veya oluştur""" |
|
|
if phone_number not in self.profiles: |
|
|
self.profiles[phone_number] = { |
|
|
"created_at": datetime.now(), |
|
|
"last_updated": datetime.now(), |
|
|
"total_messages": 0, |
|
|
"preferences": { |
|
|
"budget_min": None, |
|
|
"budget_max": None, |
|
|
"categories": [], |
|
|
"usage_purpose": [], |
|
|
"size_info": {}, |
|
|
"brand_preferences": [], |
|
|
"color_preferences": [] |
|
|
}, |
|
|
"behavior": { |
|
|
"price_sensitive": False, |
|
|
"tech_interested": False, |
|
|
"comparison_lover": False, |
|
|
"quick_decider": False, |
|
|
"research_oriented": False |
|
|
}, |
|
|
"interests": { |
|
|
"viewed_products": [], |
|
|
"compared_products": [], |
|
|
"favorite_features": [], |
|
|
"mentioned_brands": [] |
|
|
}, |
|
|
"statistics": { |
|
|
"comparison_requests": 0, |
|
|
"budget_queries": 0, |
|
|
"technical_questions": 0, |
|
|
"price_questions": 0, |
|
|
"availability_questions": 0 |
|
|
} |
|
|
} |
|
|
return self.profiles[phone_number] |
|
|
|
|
|
def analyze_message(self, phone_number: str, message: str) -> Dict: |
|
|
"""Mesajı analiz et ve profili güncelle""" |
|
|
profile = self.get_or_create_profile(phone_number) |
|
|
message_lower = message.lower() |
|
|
|
|
|
|
|
|
profile["total_messages"] += 1 |
|
|
profile["last_updated"] = datetime.now() |
|
|
|
|
|
analysis_results = { |
|
|
"budget_detected": False, |
|
|
"category_detected": False, |
|
|
"usage_detected": False, |
|
|
"size_detected": False, |
|
|
"behavior_indicators": [] |
|
|
} |
|
|
|
|
|
|
|
|
budget_info = self.extract_budget(message_lower) |
|
|
if budget_info: |
|
|
profile["preferences"]["budget_min"] = budget_info["min"] |
|
|
profile["preferences"]["budget_max"] = budget_info["max"] |
|
|
profile["statistics"]["budget_queries"] += 1 |
|
|
analysis_results["budget_detected"] = True |
|
|
|
|
|
|
|
|
detected_categories = self.detect_categories(message_lower) |
|
|
if detected_categories: |
|
|
for category in detected_categories: |
|
|
if category not in profile["preferences"]["categories"]: |
|
|
profile["preferences"]["categories"].append(category) |
|
|
analysis_results["category_detected"] = True |
|
|
|
|
|
|
|
|
usage_purposes = self.detect_usage_purpose(message_lower) |
|
|
if usage_purposes: |
|
|
for purpose in usage_purposes: |
|
|
if purpose not in profile["preferences"]["usage_purpose"]: |
|
|
profile["preferences"]["usage_purpose"].append(purpose) |
|
|
analysis_results["usage_detected"] = True |
|
|
|
|
|
|
|
|
size_info = self.extract_size_info(message_lower) |
|
|
if size_info: |
|
|
profile["preferences"]["size_info"].update(size_info) |
|
|
analysis_results["size_detected"] = True |
|
|
|
|
|
|
|
|
behavior_indicators = self.analyze_behavior(message_lower) |
|
|
for behavior, detected in behavior_indicators.items(): |
|
|
if detected: |
|
|
profile["behavior"][behavior] = True |
|
|
analysis_results["behavior_indicators"].append(behavior) |
|
|
|
|
|
|
|
|
self.update_statistics(profile, message_lower) |
|
|
|
|
|
|
|
|
self.save_profiles() |
|
|
|
|
|
return analysis_results |
|
|
|
|
|
def extract_budget(self, message: str) -> Optional[Dict]: |
|
|
"""Bütçe bilgisini çıkar""" |
|
|
for pattern in self.budget_patterns: |
|
|
match = re.search(pattern, message) |
|
|
if match: |
|
|
numbers = [g for g in match.groups() if g and g.isdigit()] |
|
|
if numbers: |
|
|
if len(numbers) == 1: |
|
|
|
|
|
budget = int(numbers[0]) |
|
|
if budget < 1000: |
|
|
budget *= 1000 |
|
|
return {"min": int(budget * 0.7), "max": budget} |
|
|
elif len(numbers) >= 2: |
|
|
|
|
|
min_budget = int(numbers[0]) |
|
|
max_budget = int(numbers[1]) |
|
|
if min_budget < 1000: |
|
|
min_budget *= 1000 |
|
|
if max_budget < 1000: |
|
|
max_budget *= 1000 |
|
|
return {"min": min_budget, "max": max_budget} |
|
|
return None |
|
|
|
|
|
def detect_categories(self, message: str) -> List[str]: |
|
|
"""Kategori tercihlerini tespit et""" |
|
|
detected = [] |
|
|
for category, keywords in self.category_keywords.items(): |
|
|
if any(keyword in message for keyword in keywords): |
|
|
detected.append(category) |
|
|
return detected |
|
|
|
|
|
def detect_usage_purpose(self, message: str) -> List[str]: |
|
|
"""Kullanım amacını tespit et""" |
|
|
detected = [] |
|
|
for purpose, keywords in self.usage_patterns.items(): |
|
|
if any(keyword in message for keyword in keywords): |
|
|
detected.append(purpose) |
|
|
return detected |
|
|
|
|
|
def extract_size_info(self, message: str) -> Dict: |
|
|
"""Boy/beden bilgisini çıkar""" |
|
|
size_info = {} |
|
|
|
|
|
|
|
|
boy_match = re.search(self.size_patterns["boy"], message) |
|
|
if boy_match: |
|
|
boy = boy_match.group(1) or boy_match.group(2) |
|
|
if boy: |
|
|
size_info["height"] = int(boy) |
|
|
|
|
|
|
|
|
kilo_match = re.search(self.size_patterns["kilo"], message) |
|
|
if kilo_match: |
|
|
kilo = kilo_match.group(1) or kilo_match.group(2) |
|
|
if kilo: |
|
|
size_info["weight"] = int(kilo) |
|
|
|
|
|
|
|
|
beden_match = re.search(self.size_patterns["beden"], message) |
|
|
if beden_match: |
|
|
beden = beden_match.group(1) or beden_match.group(2) |
|
|
if beden: |
|
|
size_info["size"] = beden.upper() |
|
|
|
|
|
return size_info |
|
|
|
|
|
def analyze_behavior(self, message: str) -> Dict[str, bool]: |
|
|
"""Davranış kalıplarını analiz et""" |
|
|
behavior = {} |
|
|
|
|
|
|
|
|
price_keywords = ["ucuz", "fiyat", "indirim", "kampanya", "ekonomik", "bütçe"] |
|
|
behavior["price_sensitive"] = any(keyword in message for keyword in price_keywords) |
|
|
|
|
|
|
|
|
tech_keywords = ["teknik", "özellik", "ağırlık", "malzeme", "karbon", "alüminyum", "vites"] |
|
|
behavior["tech_interested"] = any(keyword in message for keyword in tech_keywords) |
|
|
|
|
|
|
|
|
comparison_keywords = ["karşılaştır", "fark", "hangisi", "arası", "seçim"] |
|
|
behavior["comparison_lover"] = any(keyword in message for keyword in comparison_keywords) |
|
|
|
|
|
|
|
|
research_keywords = ["detay", "bilgi", "araştır", "inceleme", "test", "deneyim"] |
|
|
behavior["research_oriented"] = any(keyword in message for keyword in research_keywords) |
|
|
|
|
|
return behavior |
|
|
|
|
|
def update_statistics(self, profile: Dict, message: str): |
|
|
"""İstatistikleri güncelle""" |
|
|
if "karşılaştır" in message: |
|
|
profile["statistics"]["comparison_requests"] += 1 |
|
|
|
|
|
if any(word in message for word in ["fiyat", "kaç para", "ne kadar"]): |
|
|
profile["statistics"]["price_questions"] += 1 |
|
|
|
|
|
if any(word in message for word in ["stok", "var mı", "mevcut"]): |
|
|
profile["statistics"]["availability_questions"] += 1 |
|
|
|
|
|
if any(word in message for word in ["teknik", "özellik", "detay"]): |
|
|
profile["statistics"]["technical_questions"] += 1 |
|
|
|
|
|
def get_profile_summary(self, phone_number: str) -> Dict: |
|
|
"""Profil özetini döndür""" |
|
|
if phone_number not in self.profiles: |
|
|
return {"exists": False} |
|
|
|
|
|
profile = self.profiles[phone_number] |
|
|
|
|
|
|
|
|
confidence = min(profile["total_messages"] / 10.0, 1.0) |
|
|
|
|
|
summary = { |
|
|
"exists": True, |
|
|
"confidence": confidence, |
|
|
"total_messages": profile["total_messages"], |
|
|
"preferences": profile["preferences"], |
|
|
"behavior": profile["behavior"], |
|
|
"top_categories": profile["preferences"]["categories"][:3], |
|
|
"is_budget_defined": profile["preferences"]["budget_min"] is not None, |
|
|
"is_tech_savvy": profile["behavior"]["tech_interested"], |
|
|
"is_price_conscious": profile["behavior"]["price_sensitive"], |
|
|
"interaction_style": self.get_interaction_style(profile) |
|
|
} |
|
|
|
|
|
return summary |
|
|
|
|
|
def get_interaction_style(self, profile: Dict) -> str: |
|
|
"""Etkileşim stilini belirle""" |
|
|
stats = profile["statistics"] |
|
|
behavior = profile["behavior"] |
|
|
|
|
|
if stats["comparison_requests"] > 2 and behavior["research_oriented"]: |
|
|
return "analytical" |
|
|
elif behavior["price_sensitive"] and stats["budget_queries"] > 0: |
|
|
return "budget_conscious" |
|
|
elif behavior["tech_interested"] and stats["technical_questions"] > 1: |
|
|
return "technical" |
|
|
elif stats["comparison_requests"] == 0 and profile["total_messages"] < 5: |
|
|
return "decisive" |
|
|
else: |
|
|
return "balanced" |
|
|
|
|
|
def get_personalized_suggestions(self, phone_number: str, products_data: List) -> Dict: |
|
|
"""Kişiselleştirilmiş öneriler""" |
|
|
profile_summary = self.get_profile_summary(phone_number) |
|
|
|
|
|
if not profile_summary["exists"] or profile_summary["confidence"] < 0.3: |
|
|
return {"personalized": False, "reason": "insufficient_data"} |
|
|
|
|
|
suggestions = { |
|
|
"personalized": True, |
|
|
"user_style": profile_summary["interaction_style"], |
|
|
"budget_aware": profile_summary["is_budget_defined"], |
|
|
"recommendations": [] |
|
|
} |
|
|
|
|
|
|
|
|
filtered_products = products_data |
|
|
if profile_summary["preferences"]["budget_min"]: |
|
|
budget_min = profile_summary["preferences"]["budget_min"] |
|
|
budget_max = profile_summary["preferences"]["budget_max"] |
|
|
filtered_products = [ |
|
|
p for p in products_data |
|
|
if p[1][0] == "stokta" and p[1][1] and |
|
|
budget_min <= float(p[1][1]) <= budget_max |
|
|
] |
|
|
|
|
|
|
|
|
if profile_summary["top_categories"]: |
|
|
category_products = [] |
|
|
for category in profile_summary["top_categories"]: |
|
|
category_products.extend([ |
|
|
p for p in filtered_products |
|
|
if any(keyword in p[2].lower() for keyword in self.category_keywords.get(category, [])) |
|
|
]) |
|
|
if category_products: |
|
|
filtered_products = category_products |
|
|
|
|
|
suggestions["recommendations"] = filtered_products[:5] |
|
|
return suggestions |
|
|
|
|
|
|
|
|
passive_profiler = WhatsAppPassiveProfiler() |
|
|
|
|
|
def analyze_user_message(phone_number: str, message: str) -> Dict: |
|
|
"""Kullanıcı mesajını analiz et ve profili güncelle""" |
|
|
return passive_profiler.analyze_message(phone_number, message) |
|
|
|
|
|
def get_user_profile_summary(phone_number: str) -> Dict: |
|
|
"""Kullanıcı profil özetini getir""" |
|
|
return passive_profiler.get_profile_summary(phone_number) |
|
|
|
|
|
def get_personalized_recommendations(phone_number: str, products_data: List) -> Dict: |
|
|
"""Kişiselleştirilmiş öneriler getir""" |
|
|
return passive_profiler.get_personalized_suggestions(phone_number, products_data) |