Spaces:
Sleeping
Sleeping
| import pandas as pd | |
| import json | |
| import re | |
| from pydantic import BaseModel | |
| from typing import Optional, List | |
| from backend.config.setting import ( | |
| PRE_TEST_CUSTOMER_CSV, | |
| PRE_PRODUCT_JSON, | |
| PRE_LAW_JSON | |
| ) | |
| def load_data(): | |
| data = {} | |
| try: | |
| data['customers'] = pd.read_csv(PRE_TEST_CUSTOMER_CSV) | |
| data['customers'].columns = data['customers'].columns.str.strip() | |
| except Exception as e: | |
| print(e) | |
| data['customers'] = pd.DataFrame() | |
| try: | |
| with open(PRE_PRODUCT_JSON, encoding="utf-8") as f: | |
| data['products'] = json.load(f) | |
| except: | |
| data['products'] = [] | |
| try: | |
| with open(PRE_LAW_JSON, encoding="utf-8") as f: | |
| data['laws'] = json.load(f) | |
| except: | |
| data['laws'] = [] | |
| return data | |
| # ============================================================================== | |
| # 2. HÀM HỖ TRỢ LOGIC | |
| # ============================================================================== | |
| def extract_price(text): | |
| """Trích xuất giá tiền từ chuỗi mô tả (VD: '100.000đ' -> 100000)""" | |
| text = str(text).replace('.', '').replace(',', '') | |
| match = re.search(r'(\d{4,6})', text) | |
| return int(match.group(1)) if match else 0 | |
| def check_segment_rules(customer, product, p_price, current_spend): | |
| """ | |
| Logic tính điểm phù hợp cho gói cước. | |
| CHIẾN THUẬT: UPSELL TOÀN DIỆN (Luôn ưu tiên gói giá trị cao hơn/bằng) | |
| """ | |
| segment = customer.get('Segment', 'C') | |
| # churn_score = customer.get('Churn Score', 0) # Tạm thời bỏ qua Churn Score để tập trung Upsell | |
| desc_lower = product.get('desc', '').lower() | |
| score = 0 | |
| # --------------------------------------------------------- | |
| # CHIẾN THUẬT CHUNG CHO MỌI KHÁCH HÀNG: UPSELL LÀ ƯU TIÊN SỐ 1 | |
| # --------------------------------------------------------- | |
| # 1. Ưu tiên gói có giá CAO HƠN hoặc BẰNG gói hiện tại (Tăng doanh thu) | |
| if p_price >= current_spend: | |
| score += 50 # Điểm cộng rất lớn cho việc Upsell | |
| # Nếu giá cao hơn nhưng không quá đắt (trong tầm chấp nhận +20% đến +50%) | |
| if p_price <= current_spend * 1.5: | |
| score += 20 | |
| else: | |
| # Nếu gói rẻ hơn -> Downsell (Trừ điểm, trừ khi gói đó quá ngon) | |
| score -= 10 | |
| # 2. Phân loại theo Segment để cộng thêm điểm phụ | |
| if segment == 'A': # Khách VIP -> Thích Dịch vụ VIP, Data khủng | |
| if "vip" in desc_lower or "300gb" in desc_lower: score += 40 | |
| if p_price > 150000: score += 30 # Khách giàu thích gói đắt | |
| elif segment == 'B': # Khách trẻ/Văn phòng -> Thích Mạng Xã Hội | |
| if any(x in desc_lower for x in ['tiktok', 'youtube', 'facebook', 'social']): score += 40 | |
| # Gói tầm trung (90k - 150k) là sweet spot | |
| if 90000 <= p_price <= 150000: score += 20 | |
| elif segment == 'C': # Khách rủi ro -> Thích Khuyến mãi/Miễn phí (Nhưng vẫn cố Upsell) | |
| if "miễn phí" in desc_lower or "tặng" in desc_lower: score += 40 | |
| # Với khách C, nếu Upsell thì phải Upsell nhẹ (chênh lệch ít) | |
| if p_price >= current_spend and abs(p_price - current_spend) <= 20000: | |
| score += 30 # Upsell khéo léo | |
| # 3. Điểm cộng tính năng (Data theo ngày là xu hướng hot) | |
| if "gb/ngày" in desc_lower: score += 15 | |
| return max(0, score) | |
| def build_smartbot_context(customer_id): | |
| """Xây dựng Context và danh sách gói cước gợi ý""" | |
| df = data['customers'] | |
| # Tìm cột ID gói cước (thường là 'id' hoặc 'package_id') | |
| package_id_col = 'id' if 'id' in df.columns else None | |
| # Lấy thông tin khách hàng | |
| customer = df[df['Customer ID'] == customer_id] | |
| if customer.empty: return None, [] | |
| cust_info = customer.iloc[0].to_dict() | |
| # --- 1. LẤY THÔNG TIN GÓI ĐANG DÙNG --- | |
| current_pkg_id = cust_info.get(package_id_col) | |
| current_pkg_details = None | |
| if current_pkg_id: | |
| for p in data['products']: | |
| if p['id'] == current_pkg_id: | |
| current_pkg_details = p | |
| break | |
| # Xác định giá gói hiện tại để làm mốc so sánh Upsell | |
| if current_pkg_details: | |
| current_price = extract_price(current_pkg_details['desc']) | |
| current_pkg_desc_str = f"{current_pkg_details['name']} ({current_price:,}đ)" | |
| current_spend = current_price | |
| else: | |
| # Fallback nếu dữ liệu lỗi | |
| current_spend = cust_info.get('Monthly Charge', 0) | |
| # Nếu Monthly Charge trong CSV là số nhỏ (VD: 50 -> 50$), ta nhân tạm để ra VND (Demo) | |
| if current_spend < 1000: current_spend = current_spend * 2000 | |
| current_pkg_desc_str = f"Gói cước hiện tại ({int(current_spend):,}đ/tháng)" | |
| segment = cust_info.get('Segment', 'Unknown') | |
| churn_score = cust_info.get('Churn Score', 0) | |
| # --- 2. TÌM GÓI GỢI Ý (CHIẾN THUẬT UPSELL) --- | |
| recommendations = [] | |
| for p in data['products']: | |
| p_price = extract_price(p['desc']) | |
| if p_price == 0: continue | |
| # Loại bỏ gói trùng ID (Không mời lại gói đang dùng) | |
| if current_pkg_id and p['id'] == current_pkg_id: | |
| continue | |
| # Loại bỏ nếu giá trùng (nếu không có ID) -> Để tránh mời gói y hệt | |
| if not current_pkg_id and abs(p_price - current_spend) <= 1000: | |
| continue | |
| score = check_segment_rules(cust_info, p, p_price, current_spend) | |
| # Gắn nhãn trạng thái để AI dễ chém gió | |
| status_tag = "" | |
| if p_price > current_spend: status_tag = "NÂNG CẤP (UPSELL)" | |
| elif p_price == current_spend: status_tag = "NGANG GIÁ (BETTER VALUE)" | |
| else: status_tag = "TIẾT KIỆM (DOWNSELL)" | |
| recommendations.append({ | |
| "name": p['name'], | |
| "price": p_price, | |
| "score": score, | |
| "status": status_tag, | |
| "desc": p['desc'] | |
| }) | |
| # Sắp xếp theo điểm số (Score cao nhất lên đầu) | |
| recommendations.sort(key=lambda x: x['score'], reverse=True) | |
| top_prods = recommendations[:5] | |
| # --- 3. TẠO CONTEXT VĂN BẢN (CHO GEMINI/VOICEBOT) --- | |
| products_str = "" | |
| for i, p in enumerate(top_prods): | |
| recommend_label = " [ƯU TIÊN SỐ 1]" if i == 0 else "" | |
| products_str += f"- {p['name']} ({p['price']:,}đ): {p['desc']}\n -> {recommend_label} (Điểm: {p['score']} - {p['status']})\n" | |
| # Lấy thông tin pháp lý (nếu có) | |
| laws_str = "" | |
| for i, law in enumerate(data['laws']): | |
| if i >= 2: break | |
| laws_str += f"- {law.get('topic', '')}: {law.get('content_summary', '')}\n" | |
| final_context = f""" | |
| ================================================== | |
| BỐI CẢNH TƯ VẤN: Khách hàng ID {customer_id} (Phân khúc {segment}) | |
| ================================================== | |
| --- HỒ SƠ KHÁCH HÀNG --- | |
| - Đang dùng: {current_pkg_desc_str} | |
| - Mức chi tiêu hiện tại: {int(current_spend):,} VND/tháng | |
| - Phân khúc: {segment} | |
| - Rủi ro rời mạng (Churn): {churn_score}/100 | |
| --- MỤC TIÊU CHIẾN DỊCH --- | |
| - Ưu tiên hàng đầu: UPSELL (Bán gói cước giá trị cao hơn hoặc bằng). | |
| - Chỉ Downsell (bán gói rẻ hơn) nếu khách cực kỳ gay gắt về giá. | |
| - Nhấn mạnh vào: Data khủng, Ứng dụng miễn phí (Tiktok, Youtube) để khỏa lấp vấn đề giá. | |
| --- DANH SÁCH GÓI CƯỚC ĐỀ XUẤT (Đã sắp xếp theo độ ưu tiên) --- | |
| {products_str} | |
| --- THÔNG TIN BỔ TRỢ --- | |
| {laws_str} | |
| --- YÊU CẦU ĐỐI VỚI AI/VOICEBOT --- | |
| Hãy đóng vai tư vấn viên VNPT chuyên nghiệp. | |
| 1. So sánh trực tiếp gói đang dùng và gói gợi ý: "Anh/Chị đang dùng [Tên gói cũ] ({int(current_spend):,}đ), gói [Tên gói mới] ({top_prods[0]['price']:,}đ) có ưu điểm..." | |
| 2. Nêu bật sự khác biệt cụ thể (VD: Gói cũ chỉ có X GB, gói mới có Y GB). | |
| 3. Thuyết phục khách hàng chuyển đổi dựa trên lợi ích thực tế. | |
| 4. Đừng vội vàng, hấp tấp, hãy tạo cảm giác thoải mái cho khách hàng. | |
| 5. Đừng dồn ép khách hàng, hãy để họ cảm thấy đây là lựa chọn của họ. | |
| 6. Kết thúc bằng câu hỏi mở để khách hàng dễ dàng đồng ý chuyển đổi. VDu: "Anh/chị thấy gói này thế nào ạ?" | |
| 7. Hạn chế nhắc đến rủi ro rời mạng/churn score trực tiếp. | |
| 8. Hãy trả lời theo 1 đoạn văn bản hoàn chỉnh. | |
| 9. Sử dụng tiếng Việt có dấu, ngữ pháp chuẩn. | |
| 10. Đừng tách đoạn văn bản thành nhiều phần nhỏ. Hãy đảm bảo chúng sẵn sàng để có thể đọc liền mạch bởi text to speech. | |
| """ | |
| return final_context, top_prods | |
| # ============================================================================== |