File size: 11,077 Bytes
214cd33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# database.py
import sqlite3
import datetime
import json
import os

DB_NAME = "vnpt_call_center.db"

# --- 20 DATA MẪU (SOURCE OF TRUTH) ---
# Tôi đã gộp 3 nhóm A, B, C của bạn vào đây
INITIAL_DATA = [
  {"customer_id": "CUS_001", "call_id": "CALL_0001", "call_timestamp": "2025-01-20T08:15:00+07:00", "call_duration_seconds": 78, "intent": "network_issue", "sentiment": "positive", "ai_resolved": True, "upsell_success": False, "csat": 5, "cost_per_call_vnd": 2350},
  {"customer_id": "CUS_002", "call_id": "CALL_0002", "call_timestamp": "2025-01-20T08:35:00+07:00", "call_duration_seconds": 92, "intent": "cancel_package", "sentiment": "neutral", "ai_resolved": True, "upsell_success": True, "csat": 4, "cost_per_call_vnd": 2480},
  {"customer_id": "CUS_003", "call_id": "CALL_0003", "call_timestamp": "2025-01-20T09:05:00+07:00", "call_duration_seconds": 110, "intent": "competitor", "sentiment": "negative", "ai_resolved": False, "upsell_success": False, "csat": 3, "cost_per_call_vnd": 2650},
  {"customer_id": "CUS_004", "call_id": "CALL_0004", "call_timestamp": "2025-01-20T09:40:00+07:00", "call_duration_seconds": 70, "intent": "network_issue", "sentiment": "positive", "ai_resolved": True, "upsell_success": False, "csat": 5, "cost_per_call_vnd": 2300},
  {"customer_id": "CUS_005", "call_id": "CALL_0005", "call_timestamp": "2025-01-20T10:10:00+07:00", "call_duration_seconds": 88, "intent": "cancel_package", "sentiment": "neutral", "ai_resolved": True, "upsell_success": True, "csat": 4, "cost_per_call_vnd": 2450},
  {"customer_id": "CUS_006", "call_id": "CALL_0006", "call_timestamp": "2025-01-20T10:45:00+07:00", "call_duration_seconds": 80, "intent": "network_issue", "sentiment": "positive", "ai_resolved": True, "upsell_success": False, "csat": 5, "cost_per_call_vnd": 2380},
  {"customer_id": "CUS_007", "call_id": "CALL_0007", "call_timestamp": "2025-01-18T14:20:00+07:00", "call_duration_seconds": 95, "intent": "cancel_package", "sentiment": "neutral", "ai_resolved": True, "upsell_success": True, "csat": 4, "cost_per_call_vnd": 2500},
  {"customer_id": "CUS_008", "call_id": "CALL_0008", "call_timestamp": "2025-01-18T16:05:00+07:00", "call_duration_seconds": 72, "intent": "network_issue", "sentiment": "positive", "ai_resolved": True, "upsell_success": False, "csat": 5, "cost_per_call_vnd": 2320},
  {"customer_id": "CUS_009", "call_id": "CALL_0009", "call_timestamp": "2025-01-17T10:30:00+07:00", "call_duration_seconds": 105, "intent": "competitor", "sentiment": "negative", "ai_resolved": False, "upsell_success": False, "csat": 3, "cost_per_call_vnd": 2680},
  {"customer_id": "CUS_010", "call_id": "CALL_0010", "call_timestamp": "2025-01-17T15:10:00+07:00", "call_duration_seconds": 85, "intent": "network_issue", "sentiment": "positive", "ai_resolved": True, "upsell_success": False, "csat": 5, "cost_per_call_vnd": 2400},
  {"customer_id": "CUS_011", "call_id": "CALL_0011", "call_timestamp": "2025-01-16T09:45:00+07:00", "call_duration_seconds": 90, "intent": "cancel_package", "sentiment": "neutral", "ai_resolved": True, "upsell_success": True, "csat": 4, "cost_per_call_vnd": 2480},
  {"customer_id": "CUS_012", "call_id": "CALL_0012", "call_timestamp": "2025-01-16T11:20:00+07:00", "call_duration_seconds": 76, "intent": "network_issue", "sentiment": "positive", "ai_resolved": True, "upsell_success": False, "csat": 5, "cost_per_call_vnd": 2350},
  {"customer_id": "CUS_013", "call_id": "CALL_0013", "call_timestamp": "2025-01-15T14:55:00+07:00", "call_duration_seconds": 108, "intent": "competitor", "sentiment": "negative", "ai_resolved": False, "upsell_success": False, "csat": 3, "cost_per_call_vnd": 2620},
  {"customer_id": "CUS_014", "call_id": "CALL_0014", "call_timestamp": "2025-01-15T16:40:00+07:00", "call_duration_seconds": 82, "intent": "network_issue", "sentiment": "positive", "ai_resolved": True, "upsell_success": False, "csat": 5, "cost_per_call_vnd": 2380},
  {"customer_id": "CUS_015", "call_id": "CALL_0015", "call_timestamp": "2025-01-10T10:15:00+07:00", "call_duration_seconds": 88, "intent": "cancel_package", "sentiment": "neutral", "ai_resolved": True, "upsell_success": True, "csat": 4, "cost_per_call_vnd": 2450},
  {"customer_id": "CUS_016", "call_id": "CALL_0016", "call_timestamp": "2025-01-08T14:30:00+07:00", "call_duration_seconds": 74, "intent": "network_issue", "sentiment": "positive", "ai_resolved": True, "upsell_success": False, "csat": 5, "cost_per_call_vnd": 2350},
  {"customer_id": "CUS_017", "call_id": "CALL_0017", "call_timestamp": "2025-01-05T09:50:00+07:00", "call_duration_seconds": 112, "intent": "competitor", "sentiment": "negative", "ai_resolved": False, "upsell_success": False, "csat": 3, "cost_per_call_vnd": 2700},
  {"customer_id": "CUS_018", "call_id": "CALL_0018", "call_timestamp": "2025-01-04T15:05:00+07:00", "call_duration_seconds": 80, "intent": "network_issue", "sentiment": "positive", "ai_resolved": True, "upsell_success": False, "csat": 5, "cost_per_call_vnd": 2380},
  {"customer_id": "CUS_019", "call_id": "CALL_0019", "call_timestamp": "2025-01-03T11:40:00+07:00", "call_duration_seconds": 96, "intent": "cancel_package", "sentiment": "neutral", "ai_resolved": True, "upsell_success": True, "csat": 4, "cost_per_call_vnd": 2500},
  {"customer_id": "CUS_020", "call_id": "CALL_0020", "call_timestamp": "2024-12-28T16:10:00+07:00", "call_duration_seconds": 85, "intent": "network_issue", "sentiment": "positive", "ai_resolved": True, "upsell_success": False, "csat": 5, "cost_per_call_vnd": 2400}
]

class Database:
    def __init__(self):
        self.conn = None
        self._init_db()

    def _get_conn(self):
        # Kết nối tới file SQLite, check_same_thread=False để dùng với FastAPI async
        return sqlite3.connect(DB_NAME, check_same_thread=False)

    def _init_db(self):
        conn = self._get_conn()
        cursor = conn.cursor()
        
        # Tạo bảng nếu chưa có
        cursor.execute('''

            CREATE TABLE IF NOT EXISTS calls (

                id INTEGER PRIMARY KEY AUTOINCREMENT,

                customer_id TEXT,

                call_id TEXT,

                call_timestamp DATETIME,

                duration INTEGER,

                intent TEXT,

                sentiment TEXT,

                ai_resolved BOOLEAN,

                upsell BOOLEAN,

                csat INTEGER,

                cost INTEGER

            )

        ''')
        
        # Kiểm tra xem có dữ liệu chưa
        cursor.execute("SELECT count(*) FROM calls")
        count = cursor.fetchone()[0]
        
        if count == 0:
            print(" [DB] Database trống. Đang nạp 20 bản ghi mẫu...")
            self._seed_data(cursor)
            conn.commit()
        
        conn.close()

    def _seed_data(self, cursor):
        for item in INITIAL_DATA:
            cursor.execute('''

                INSERT INTO calls (customer_id, call_id, call_timestamp, duration, intent, sentiment, ai_resolved, upsell, csat, cost)

                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

            ''', (
                item['customer_id'],
                item['call_id'],
                item['call_timestamp'],
                item['call_duration_seconds'],
                item['intent'],
                item['sentiment'],
                item['ai_resolved'],
                item['upsell_success'],
                item['csat'],
                item['cost_per_call_vnd']
            ))

    # --- HÀM THÊM MỚI (Dùng cho Live Call) ---
    def add_call(self, customer_id, duration, intent, sentiment, ai_resolved, upsell, cost):
        conn = self._get_conn()
        cursor = conn.cursor()
        
        # Tạo call_id tự động (LIVE_timestamp)
        call_id = f"LIVE_{int(datetime.datetime.now().timestamp())}"
        timestamp = datetime.datetime.now().isoformat()

        cursor.execute('''

            INSERT INTO calls (customer_id, call_id, call_timestamp, duration, intent, sentiment, ai_resolved, upsell, csat, cost)

            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

        ''', (customer_id, call_id, timestamp, duration, intent, sentiment, ai_resolved, upsell, cost['csat'], cost['value']))
        # Lưu ý: tham số cost ở đây tôi tách ra dict csat/value ở logic_flow (xem bước 2)
        
        conn.commit()
        conn.close()
    # [THÊM MỚI] Hàm cập nhật đánh giá thực tế từ người dùng
    def update_call_rating(self, customer_id, stars, note):
        conn = self._get_conn()
        cursor = conn.cursor()
        
        try:
            # 1. Tìm ID cuộc gọi mới nhất của khách hàng này
            cursor.execute('''

                SELECT id FROM calls 

                WHERE customer_id = ? 

                ORDER BY call_timestamp DESC 

                LIMIT 1

            ''', (str(customer_id),))
            
            row = cursor.fetchone()
            
            if row:
                call_id_db = row[0]
                # 2. Cập nhật điểm CSAT và ghi chú (nếu muốn lưu note thì cần thêm cột note vào bảng, tạm thời ta chỉ update CSAT)
                # Lưu ý: Nếu bạn muốn lưu cả 'note', bạn cần ALTER TABLE hoặc tạo lại DB mới có cột 'note'.
                # Ở đây tôi giả định chỉ update CSAT cho đơn giản.
                cursor.execute('''

                    UPDATE calls 

                    SET csat = ? 

                    WHERE id = ?

                ''', (int(stars), call_id_db))
                
                conn.commit()
                print(f" [DB] Đã cập nhật CSAT={stars} cho khách {customer_id} (DB ID: {call_id_db})")
            else:
                print(f" [DB] Không tìm thấy cuộc gọi nào của khách {customer_id} để update.")
                
        except Exception as e:
            print(f" [DB Error] Update Rating: {e}")
        finally:
            conn.close()
    # --- HÀM LẤY DỮ LIỆU CHO DASHBOARD ---
    def get_all_calls(self):
        conn = self._get_conn()
        cursor = conn.cursor()
        
        # Lấy tất cả, sắp xếp mới nhất lên đầu
        cursor.execute("SELECT * FROM calls ORDER BY call_timestamp DESC")
        rows = cursor.fetchall()
        
        # Convert tuple sang list of dict để trả về JSON
        result = []
        for r in rows:
            result.append({
                "id": r[1],          # customer_id
                "time": r[3],        # timestamp
                "dur": r[4],         # duration
                "intent": r[5],
                "sent": r[6],
                "ai": bool(r[7]),
                "upsell": bool(r[8]),
                "csat": r[9],
                "cost": r[10]
            })
        
        conn.close()
        return result

# Khởi tạo singleton
db = Database()