akra35567 commited on
Commit
85b104e
·
1 Parent(s): 73830e7

Update modules/database.py

Browse files
Files changed (1) hide show
  1. modules/database.py +474 -481
modules/database.py CHANGED
@@ -1,481 +1,474 @@
1
- import sqlite3
2
- import json
3
- import time
4
- from typing import Optional, List, Dict, Any
5
-
6
- class Database:
7
- def __init__(self, db_path):
8
- self.db_path = db_path
9
- self.max_retries = 5
10
- self.retry_delay = 0.1
11
- self._init_db()
12
-
13
- def _get_connection(self) -> sqlite3.Connection:
14
- """Get database connection with retry logic"""
15
- for attempt in range(self.max_retries):
16
- try:
17
- conn = sqlite3.connect(self.db_path, timeout=30.0)
18
- # Enable WAL mode for better concurrency
19
- conn.execute('PRAGMA journal_mode=WAL')
20
- conn.execute('PRAGMA synchronous=NORMAL')
21
- conn.execute('PRAGMA cache_size=1000')
22
- conn.execute('PRAGMA temp_store=MEMORY')
23
- conn.execute('PRAGMA busy_timeout=30000') # 30 seconds
24
- return conn
25
- except sqlite3.OperationalError as e:
26
- if "database is locked" in str(e) and attempt < self.max_retries - 1:
27
- time.sleep(self.retry_delay * (2 ** attempt)) # Exponential backoff
28
- continue
29
- raise e
30
- raise sqlite3.OperationalError("Failed to acquire database connection after retries")
31
-
32
- def _execute_with_retry(self, query: str, params: Optional[tuple] = None, commit: bool = False) -> Optional[List[tuple]]:
33
- """Execute query with retry logic"""
34
- for attempt in range(self.max_retries):
35
- try:
36
- with self._get_connection() as conn:
37
- c = conn.cursor()
38
- if params:
39
- c.execute(query, params)
40
- else:
41
- c.execute(query)
42
-
43
- result = c.fetchall() if query.strip().upper().startswith('SELECT') else None
44
-
45
- if commit:
46
- conn.commit()
47
-
48
- return result
49
- except sqlite3.OperationalError as e:
50
- if "database is locked" in str(e) and attempt < self.max_retries - 1:
51
- time.sleep(self.retry_delay * (2 ** attempt))
52
- continue
53
- raise e
54
- raise sqlite3.OperationalError("Failed to execute query after retries")
55
-
56
- def _init_db(self):
57
- with self._get_connection() as conn:
58
- c = conn.cursor()
59
-
60
- # Tabela de aprendizado
61
- c.execute('''CREATE TABLE IF NOT EXISTS aprendizado (
62
- id INTEGER PRIMARY KEY AUTOINCREMENT,
63
- usuario TEXT,
64
- dado TEXT,
65
- valor TEXT
66
- )''')
67
- # Tabela de exemplos
68
- c.execute('''CREATE TABLE IF NOT EXISTS exemplos (
69
- id INTEGER PRIMARY KEY AUTOINCREMENT,
70
- tipo TEXT NOT NULL,
71
- entrada TEXT NOT NULL,
72
- resposta TEXT NOT NULL
73
- )''')
74
- # Tabela info_geral
75
- c.execute('''CREATE TABLE IF NOT EXISTS info_geral (
76
- chave TEXT PRIMARY KEY,
77
- valor TEXT NOT NULL
78
- )''')
79
- # Tabela estilos
80
- c.execute('''CREATE TABLE IF NOT EXISTS estilos (
81
- numero_usuario TEXT PRIMARY KEY,
82
- estilo TEXT NOT NULL
83
- )''')
84
- # Tabela preferencias_tom
85
- c.execute('''CREATE TABLE IF NOT EXISTS preferencias_tom (
86
- numero_usuario TEXT PRIMARY KEY,
87
- tom TEXT NOT NULL
88
- )''')
89
- # Tabela afinidades
90
- c.execute('''CREATE TABLE IF NOT EXISTS afinidades (
91
- numero_usuario TEXT PRIMARY KEY,
92
- afinidade REAL NOT NULL
93
- )''')
94
- # Tabela termos
95
- c.execute('''CREATE TABLE IF NOT EXISTS termos (
96
- id INTEGER PRIMARY KEY AUTOINCREMENT,
97
- numero_usuario TEXT NOT NULL,
98
- termo TEXT NOT NULL,
99
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
100
- )''')
101
- # Tabela aprendizados (versão detalhada)
102
- c.execute('''CREATE TABLE IF NOT EXISTS aprendizados (
103
- id INTEGER PRIMARY KEY AUTOINCREMENT,
104
- numero_usuario TEXT NOT NULL,
105
- chave TEXT NOT NULL,
106
- valor TEXT NOT NULL,
107
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
108
- )''')
109
- # Tabela vocabulário_patenteado
110
- c.execute('''CREATE TABLE IF NOT EXISTS vocabulario_patenteado (
111
- termo TEXT PRIMARY KEY,
112
- definicao TEXT NOT NULL,
113
- uso TEXT NOT NULL,
114
- exemplo TEXT NOT NULL
115
- )''')
116
- # Tabela usuarios_privilegiados
117
- c.execute('''CREATE TABLE IF NOT EXISTS usuarios_privilegiados (
118
- numero_usuario TEXT PRIMARY KEY,
119
- nome TEXT NOT NULL
120
- )''')
121
- # Tabela whatsapp_ids
122
- c.execute('''CREATE TABLE IF NOT EXISTS whatsapp_ids (
123
- id INTEGER PRIMARY KEY AUTOINCREMENT,
124
- whatsapp_id TEXT NOT NULL,
125
- sender_number TEXT NOT NULL,
126
- UNIQUE (whatsapp_id, sender_number)
127
- )''')
128
- # Tabela embeddings
129
- c.execute('''CREATE TABLE IF NOT EXISTS embeddings (
130
- id INTEGER PRIMARY KEY AUTOINCREMENT,
131
- texto TEXT NOT NULL,
132
- embedding BLOB NOT NULL
133
- )''')
134
-
135
- # Tabela mensagens (definição completa e única)
136
- c.execute('''CREATE TABLE IF NOT EXISTS mensagens (
137
- id INTEGER PRIMARY KEY AUTOINCREMENT,
138
- usuario TEXT NOT NULL,
139
- mensagem TEXT NOT NULL,
140
- resposta TEXT NOT NULL,
141
- numero TEXT,
142
- is_reply BOOLEAN DEFAULT 0,
143
- mensagem_original TEXT,
144
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
145
- data TIMESTAMP DEFAULT CURRENT_TIMESTAMP
146
- )''')
147
-
148
- # Nova tabela: emocao_exemplos - para armazenar exemplos de emoções
149
- c.execute('''CREATE TABLE IF NOT EXISTS emocao_exemplos (
150
- id INTEGER PRIMARY KEY AUTOINCREMENT,
151
- emocao TEXT NOT NULL,
152
- entrada TEXT NOT NULL,
153
- resposta TEXT NOT NULL,
154
- tom TEXT NOT NULL,
155
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
156
- )''')
157
-
158
- # Nova tabela: girias_aprendidas - para armazenar gírias aprendidas
159
- c.execute('''CREATE TABLE IF NOT EXISTS girias_aprendidas (
160
- id INTEGER PRIMARY KEY AUTOINCREMENT,
161
- numero_usuario TEXT NOT NULL,
162
- giria TEXT NOT NULL,
163
- significado TEXT NOT NULL,
164
- contexto TEXT,
165
- frequencia INTEGER DEFAULT 1,
166
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
167
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
168
- )''')
169
-
170
- # Nova tabela: tom_usuario - para rastrear tom do usuário
171
- c.execute('''CREATE TABLE IF NOT EXISTS tom_usuario (
172
- id INTEGER PRIMARY KEY AUTOINCREMENT,
173
- numero_usuario TEXT NOT NULL,
174
- tom_detectado TEXT NOT NULL,
175
- intensidade REAL DEFAULT 0.5,
176
- contexto TEXT,
177
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
178
- )''')
179
-
180
- # Nova tabela: adaptacao_dinamica - para armazenar adaptações dinâmicas
181
- c.execute('''CREATE TABLE IF NOT EXISTS adaptacao_dinamica (
182
- id INTEGER PRIMARY KEY AUTOINCREMENT,
183
- numero_usuario TEXT NOT NULL,
184
- tipo_adaptacao TEXT NOT NULL,
185
- valor_anterior TEXT,
186
- valor_novo TEXT,
187
- razao TEXT,
188
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
189
- )''')
190
-
191
- conn.commit()
192
-
193
- # Migration melhorada: verificar e adicionar colunas necessárias
194
- self._migrate_mensagens_table(c, conn)
195
-
196
- def _migrate_mensagens_table(self, cursor, conn):
197
- """Migração específica para garantir que a tabela mensagens tenha todas as colunas necessárias"""
198
- try:
199
- cursor.execute("PRAGMA table_info('mensagens')")
200
- existing_cols = {row[1] for row in cursor.fetchall()}
201
-
202
- # Colunas que devem existir
203
- required_cols = {
204
- 'numero': "ALTER TABLE mensagens ADD COLUMN numero TEXT",
205
- 'is_reply': "ALTER TABLE mensagens ADD COLUMN is_reply BOOLEAN DEFAULT 0",
206
- 'mensagem_original': "ALTER TABLE mensagens ADD COLUMN mensagem_original TEXT",
207
- 'created_at': "ALTER TABLE mensagens ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP",
208
- 'data': "ALTER TABLE mensagens ADD COLUMN data TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
209
- }
210
-
211
- # Adicionar colunas que não existem
212
- for col_name, alter_stmt in required_cols.items():
213
- if col_name not in existing_cols:
214
- try:
215
- cursor.execute(alter_stmt)
216
- conn.commit()
217
- print(f"Coluna '{col_name}' adicionada à tabela mensagens")
218
- except sqlite3.Error as e:
219
- print(f"Erro ao adicionar coluna '{col_name}': {e}")
220
-
221
- except sqlite3.Error as e:
222
- print(f"Erro durante migração da tabela mensagens: {e}")
223
-
224
- # Métodos para EXEMPLOS
225
- def inserir_exemplo(self, tipo, entrada, resposta):
226
- self._execute_with_retry("INSERT INTO exemplos (tipo, entrada, resposta) VALUES (?, ?, ?)", (tipo, entrada, resposta), commit=True)
227
-
228
- def recuperar_exemplos(self, tipo=None):
229
- if tipo:
230
- return self._execute_with_retry("SELECT entrada, resposta FROM exemplos WHERE tipo=? ORDER BY id DESC", (tipo,))
231
- else:
232
- return self._execute_with_retry("SELECT entrada, resposta FROM exemplos ORDER BY id DESC")
233
-
234
- # Métodos para INFO_GERAL
235
- def salvar_info_geral(self, chave, valor):
236
- self._execute_with_retry("INSERT OR REPLACE INTO info_geral (chave, valor) VALUES (?, ?)", (chave, valor), commit=True)
237
-
238
- def recuperar_info_geral(self, chave):
239
- result = self._execute_with_retry("SELECT valor FROM info_geral WHERE chave=?", (chave,))
240
- return result[0][0] if result else None
241
-
242
- # Métodos para ESTILOS
243
- def salvar_estilo(self, numero_usuario, estilo):
244
- self._execute_with_retry("INSERT OR REPLACE INTO estilos (numero_usuario, estilo) VALUES (?, ?)", (numero_usuario, estilo), commit=True)
245
-
246
- def recuperar_estilo(self, numero_usuario):
247
- result = self._execute_with_retry("SELECT estilo FROM estilos WHERE numero_usuario=?", (numero_usuario,))
248
- return result[0][0] if result else None
249
-
250
- # Métodos para PREFERENCIAS_TOM
251
- def salvar_preferencia_tom(self, numero_usuario, tom):
252
- self._execute_with_retry("INSERT OR REPLACE INTO preferencias_tom (numero_usuario, tom) VALUES (?, ?)", (numero_usuario, tom), commit=True)
253
-
254
- def recuperar_preferencia_tom(self, numero_usuario):
255
- result = self._execute_with_retry("SELECT tom FROM preferencias_tom WHERE numero_usuario=?", (numero_usuario,))
256
- return result[0][0] if result else None
257
-
258
- # Métodos para AFINIDADES
259
- def salvar_afinidade(self, numero_usuario, afinidade):
260
- self._execute_with_retry("INSERT OR REPLACE INTO afinidades (numero_usuario, afinidade) VALUES (?, ?)", (numero_usuario, afinidade), commit=True)
261
-
262
- def recuperar_afinidade(self, numero_usuario):
263
- result = self._execute_with_retry("SELECT afinidade FROM afinidades WHERE numero_usuario=?", (numero_usuario,))
264
- return result[0][0] if result else None
265
-
266
- # Métodos para TERMOS
267
- def registrar_termo(self, numero_usuario, termo):
268
- self._execute_with_retry("INSERT INTO termos (numero_usuario, termo) VALUES (?, ?)", (numero_usuario, termo), commit=True)
269
-
270
- def recuperar_termos(self, numero_usuario):
271
- result = self._execute_with_retry("SELECT termo FROM termos WHERE numero_usuario=? ORDER BY created_at DESC", (numero_usuario,))
272
- return [row[0] for row in result] if result else []
273
-
274
- # Métodos para APRENDIZADOS (detalhado)
275
- def salvar_aprendizado_detalhado(self, numero_usuario, chave, valor):
276
- self._execute_with_retry("INSERT INTO aprendizados (numero_usuario, chave, valor) VALUES (?, ?, ?)", (numero_usuario, chave, valor), commit=True)
277
-
278
- def recuperar_aprendizado_detalhado(self, numero_usuario, chave=None):
279
- if chave:
280
- result = self._execute_with_retry("SELECT valor FROM aprendizados WHERE numero_usuario=? AND chave=? ORDER BY created_at DESC LIMIT 1", (numero_usuario, chave))
281
- return result[0][0] if result else None
282
- else:
283
- return self._execute_with_retry("SELECT chave, valor FROM aprendizados WHERE numero_usuario=? ORDER BY created_at DESC", (numero_usuario,))
284
-
285
- # Métodos para VOCABULARIO_PATENTEADO
286
- def registrar_vocabulario_patenteado(self, termo, definicao, uso, exemplo):
287
- self._execute_with_retry("INSERT OR REPLACE INTO vocabulario_patenteado (termo, definicao, uso, exemplo) VALUES (?, ?, ?, ?)", (termo, definicao, uso, exemplo), commit=True)
288
-
289
- def recuperar_vocabulario_patenteado(self, termo):
290
- result = self._execute_with_retry("SELECT definicao, uso, exemplo FROM vocabulario_patenteado WHERE termo=?", (termo,))
291
- return result[0] if result else None
292
-
293
- # Métodos para USUARIOS_PRIVILEGIADOS
294
- def adicionar_usuario_privilegiado(self, numero_usuario, nome):
295
- self._execute_with_retry("INSERT OR REPLACE INTO usuarios_privilegiados (numero_usuario, nome) VALUES (?, ?)", (numero_usuario, nome), commit=True)
296
-
297
- def verificar_usuario_privilegiado(self, numero_usuario):
298
- result = self._execute_with_retry("SELECT nome FROM usuarios_privilegiados WHERE numero_usuario=?", (numero_usuario,))
299
- return result[0][0] if result else None
300
-
301
- # Métodos para WHATSAPP_IDS
302
- def registrar_whatsapp_id(self, whatsapp_id, sender_number):
303
- self._execute_with_retry("INSERT OR IGNORE INTO whatsapp_ids (whatsapp_id, sender_number) VALUES (?, ?)", (whatsapp_id, sender_number), commit=True)
304
-
305
- def recuperar_whatsapp_ids(self, sender_number):
306
- result = self._execute_with_retry("SELECT whatsapp_id FROM whatsapp_ids WHERE sender_number=?", (sender_number,))
307
- return [row[0] for row in result] if result else []
308
-
309
- # Métodos para EMBEDDINGS
310
- def salvar_embedding(self, texto, embedding_bytes):
311
- self._execute_with_retry("INSERT INTO embeddings (texto, embedding) VALUES (?, ?)", (texto, embedding_bytes), commit=True)
312
-
313
- def recuperar_embedding(self, texto):
314
- result = self._execute_with_retry("SELECT embedding FROM embeddings WHERE texto=? ORDER BY id DESC LIMIT 1", (texto,))
315
- return result[0][0] if result else None
316
-
317
- # Métodos para MENSAGENS
318
- def salvar_mensagem(self, usuario, mensagem, resposta, numero=None, is_reply=False, mensagem_original=None):
319
- """Salva mensagem com tratamento de erro robusto"""
320
- try:
321
- # Verificar se todas as colunas existem antes de inserir
322
- existing_cols_result = self._execute_with_retry("PRAGMA table_info('mensagens')")
323
- existing_cols = {row[1] for row in existing_cols_result} if existing_cols_result else set()
324
-
325
- # Construir query dinamicamente baseada nas colunas existentes
326
- base_cols = ['usuario', 'mensagem', 'resposta']
327
- base_values = [usuario, mensagem, resposta]
328
-
329
- optional_cols = {
330
- 'numero': numero,
331
- 'is_reply': is_reply,
332
- 'mensagem_original': mensagem_original
333
- }
334
-
335
- # Adicionar colunas opcionais que existem na tabela
336
- for col, val in optional_cols.items():
337
- if col in existing_cols:
338
- base_cols.append(col)
339
- base_values.append(val)
340
-
341
- # Construir e executar query
342
- placeholders = ', '.join(['?' for _ in base_cols])
343
- cols_str = ', '.join(base_cols)
344
-
345
- query = f"INSERT INTO mensagens ({cols_str}) VALUES ({placeholders})"
346
- self._execute_with_retry(query, tuple(base_values), commit=True)
347
-
348
- except sqlite3.Error as e:
349
- print(f"Erro ao salvar mensagem: {e}")
350
- # Fallback: tentar salvar apenas com colunas básicas
351
- try:
352
- self._execute_with_retry("INSERT INTO mensagens (usuario, mensagem, resposta) VALUES (?, ?, ?)", (usuario, mensagem, resposta), commit=True)
353
- except sqlite3.Error as fallback_error:
354
- print(f"Erro no fallback ao salvar mensagem: {fallback_error}")
355
-
356
- def recuperar_mensagens(self, usuario, limite=5):
357
- return self._execute_with_retry("SELECT mensagem, resposta FROM mensagens WHERE usuario=? ORDER BY id DESC LIMIT ?", (usuario, limite))
358
-
359
- # Métodos para APRENDIZADO
360
- def salvar_aprendizado(self, usuario, dado, valor):
361
- self._execute_with_retry("INSERT INTO aprendizado (usuario, dado, valor) VALUES (?, ?, ?)", (usuario, dado, valor), commit=True)
362
-
363
- def recuperar_aprendizado(self, usuario, dado):
364
- result = self._execute_with_retry("SELECT valor FROM aprendizado WHERE usuario=? AND dado=? ORDER BY id DESC LIMIT 1", (usuario, dado))
365
- return result[0][0] if result else None
366
-
367
- # NOVOS MÉTODOS PARA APRENDIZADO AVANÇADO
368
-
369
- # Métodos para EMOCAO_EXEMPLOS
370
- def salvar_exemplo_emocao(self, emocao: str, entrada: str, resposta: str, tom: str):
371
- """Salva exemplo de resposta para uma emoção específica"""
372
- self._execute_with_retry("INSERT INTO emocao_exemplos (emocao, entrada, resposta, tom) VALUES (?, ?, ?, ?)", (emocao, entrada, resposta, tom), commit=True)
373
-
374
- def recuperar_exemplos_emocao(self, emocao: str) -> List[Dict[str, Any]]:
375
- """Recupera exemplos de resposta para uma emoção"""
376
- result = self._execute_with_retry("SELECT entrada, resposta, tom FROM emocao_exemplos WHERE emocao=? ORDER BY created_at DESC", (emocao,))
377
- return [{"entrada": row[0], "resposta": row[1], "tom": row[2]} for row in result] if result else []
378
-
379
- # Métodos para GIRIAS_APRENDIDAS
380
- def salvar_giria_aprendida(self, numero_usuario: str, giria: str, significado: str, contexto: Optional[str] = None):
381
- """Salva ou atualiza uma gíria aprendida"""
382
- # Verificar se existe
383
- existing = self._execute_with_retry("SELECT id, frequencia FROM girias_aprendidas WHERE numero_usuario=? AND giria=?", (numero_usuario, giria))
384
- if existing:
385
- # Atualizar frequência
386
- self._execute_with_retry("UPDATE girias_aprendidas SET frequencia=frequencia+1, updated_at=CURRENT_TIMESTAMP WHERE id=?", (existing[0][0],), commit=True)
387
- else:
388
- # Inserir nova
389
- self._execute_with_retry("INSERT INTO girias_aprendidas (numero_usuario, giria, significado, contexto) VALUES (?, ?, ?, ?)", (numero_usuario, giria, significado, contexto), commit=True)
390
-
391
- def recuperar_girias_usuario(self, numero_usuario: str) -> List[Dict[str, Any]]:
392
- """Recupera todas as gírias aprendidas de um usuário"""
393
- result = self._execute_with_retry("SELECT giria, significado, contexto, frequencia FROM girias_aprendidas WHERE numero_usuario=? ORDER BY frequencia DESC", (numero_usuario,))
394
- return [{"giria": row[0], "significado": row[1], "contexto": row[2], "frequencia": row[3]} for row in result] if result else []
395
-
396
- def buscar_significado_giria(self, numero_usuario: str, giria: str) -> Optional[str]:
397
- """Busca o significado de uma gíria específica"""
398
- result = self._execute_with_retry("SELECT significado FROM girias_aprendidas WHERE numero_usuario=? AND giria=?", (numero_usuario, giria))
399
- return result[0][0] if result else None
400
-
401
- # Métodos para TOM_USUARIO
402
- def registrar_tom_usuario(self, numero_usuario: str, tom_detectado: str, intensidade: float = 0.5, contexto: Optional[str] = None):
403
- """Registra detecção de tom do usuário"""
404
- self._execute_with_retry("INSERT INTO tom_usuario (numero_usuario, tom_detectado, intensidade, contexto) VALUES (?, ?, ?, ?)", (numero_usuario, tom_detectado, intensidade, contexto), commit=True)
405
-
406
- def obter_tom_predominante(self, numero_usuario: str, limite: int = 10) -> Optional[str]:
407
- """Obtém o tom predominante do usuário baseado no histórico recente"""
408
- result = self._execute_with_retry("""
409
- SELECT tom_detectado, COUNT(*) as count
410
- FROM tom_usuario
411
- WHERE numero_usuario=?
412
- ORDER BY created_at DESC
413
- LIMIT ?
414
- GROUP BY tom_detectado
415
- ORDER BY count DESC
416
- LIMIT 1
417
- """, (numero_usuario, limite))
418
- return result[0][0] if result else None
419
-
420
- # Métodos para ADAPTACAO_DINAMICA
421
- def registrar_adaptacao(self, numero_usuario: str, tipo_adaptacao: str, valor_anterior: str, valor_novo: str, razao: str):
422
- """Registra uma adaptação dinâmica feita pela IA"""
423
- self._execute_with_retry("INSERT INTO adaptacao_dinamica (numero_usuario, tipo_adaptacao, valor_anterior, valor_novo, razao) VALUES (?, ?, ?, ?, ?)", (numero_usuario, tipo_adaptacao, valor_anterior, valor_novo, razao), commit=True)
424
-
425
- def obter_adaptacoes_usuario(self, numero_usuario: str, tipo_adaptacao: Optional[str] = None) -> List[Dict[str, Any]]:
426
- """Obtém histórico de adaptações de um usuário"""
427
- if tipo_adaptacao:
428
- result = self._execute_with_retry("SELECT tipo_adaptacao, valor_anterior, valor_novo, razao, created_at FROM adaptacao_dinamica WHERE numero_usuario=? AND tipo_adaptacao=? ORDER BY created_at DESC", (numero_usuario, tipo_adaptacao))
429
- else:
430
- result = self._execute_with_retry("SELECT tipo_adaptacao, valor_anterior, valor_novo, razao, created_at FROM adaptacao_dinamica WHERE numero_usuario=? ORDER BY created_at DESC", (numero_usuario,))
431
- return [{"tipo": row[0], "anterior": row[1], "novo": row[2], "razao": row[3], "data": row[4]} for row in result] if result else []
432
-
433
- # Método auxiliar para análise de emoções em mensagens
434
- def analisar_emocoes_mensagem(self, mensagem: str) -> Dict[str, Any]:
435
- """Analisa emoções presentes em uma mensagem"""
436
- mensagem_lower = mensagem.lower()
437
-
438
- # Palavras-chave por emoção
439
- emocao_keywords = {
440
- "feliz": ["feliz", "alegre", "contente", "satisfeito", "ótimo", "bom", "maravilhoso", "incrível", "show", "top"],
441
- "triste": ["triste", "deprimido", "chateado", "desanimado", "péssimo", "ruim", "horrível"],
442
- "raiva": ["raiva", "irritado", "furioso", "puto", "nervoso", "estressado", "odiando"],
443
- "medo": ["medo", "assustado", "preocupado", "ansioso", "nervoso", "temendo"],
444
- "surpresa": ["surpreso", "uau", "caramba", "incrível", "impressionado", "wow"],
445
- "nojo": ["nojo", "repugnante", "horrível", "detesto", "odeio", "repulsivo"],
446
- "amor": ["amo", "adoro", "gosto", "apaixonado", "carinhoso", "afetuoso"],
447
- "neutro": [] # fallback
448
- }
449
-
450
- # Detectar emoções
451
- emocao_detectada = "neutro"
452
- intensidade_max = 0
453
-
454
- for emocao, keywords in emocao_keywords.items():
455
- if emocao == "neutro":
456
- continue
457
- count = sum(1 for keyword in keywords if keyword in mensagem_lower)
458
- if count > intensidade_max:
459
- intensidade_max = count
460
- emocao_detectada = emocao
461
-
462
- return {
463
- "emocao": emocao_detectada,
464
- "intensidade": min(intensidade_max / 3.0, 1.0), # Normalizar para 0-1
465
- "palavras_chave_encontradas": intensidade_max
466
- }
467
-
468
- # Método para obter contexto completo de aprendizado de usuário
469
- def obter_contexto_aprendizado(self, numero_usuario: str) -> Dict[str, Any]:
470
- """Obtém contexto completo de aprendizado de um usuário para adaptação dinâmica"""
471
- contexto = {
472
- "girias": self.recuperar_girias_usuario(numero_usuario),
473
- "tom_predominante": self.obter_tom_predominante(numero_usuario),
474
- "estilo": self.recuperar_estilo(numero_usuario),
475
- "preferencia_tom": self.recuperar_preferencia_tom(numero_usuario),
476
- "afinidade": self.recuperar_afinidade(numero_usuario),
477
- "adaptacoes_recentes": self.obter_adaptacoes_usuario(numero_usuario)[-5:], # Últimas 5
478
- "aprendizados_detalhados": self.recuperar_aprendizado_detalhado(numero_usuario)
479
- }
480
-
481
- return contexto
 
1
+ import sqlite3
2
+ import json
3
+ import time
4
+ import logging
5
+ from typing import Optional, List, Dict, Any, Tuple
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class Database:
11
+ def __init__(self, db_path: str):
12
+ self.db_path = db_path
13
+ self.max_retries = 5
14
+ self.retry_delay = 0.1
15
+ self._init_db()
16
+ self._ensure_all_columns_and_indexes()
17
+
18
+ # ================================================================
19
+ # CONEXÃO COM RETRY + WAL
20
+ # ================================================================
21
+
22
+ def _get_connection(self) -> sqlite3.Connection:
23
+ for attempt in range(self.max_retries):
24
+ try:
25
+ conn = sqlite3.connect(self.db_path, timeout=30.0, check_same_thread=False)
26
+ conn.execute('PRAGMA journal_mode=WAL')
27
+ conn.execute('PRAGMA synchronous=NORMAL')
28
+ conn.execute('PRAGMA cache_size=1000')
29
+ conn.execute('PRAGMA temp_store=MEMORY')
30
+ conn.execute('PRAGMA busy_timeout=30000')
31
+ conn.execute('PRAGMA foreign_keys=ON')
32
+ return conn
33
+ except sqlite3.OperationalError as e:
34
+ if "database is locked" in str(e) and attempt < self.max_retries - 1:
35
+ time.sleep(self.retry_delay * (2 ** attempt))
36
+ continue
37
+ raise
38
+ raise sqlite3.OperationalError("Failed to acquire connection after retries")
39
+
40
+ def _execute_with_retry(self, query: str, params: Optional[tuple] = None, commit: bool = False) -> Optional[List[Tuple]]:
41
+ for attempt in range(self.max_retries):
42
+ try:
43
+ with self._get_connection() as conn:
44
+ c = conn.cursor()
45
+ if params:
46
+ c.execute(query, params)
47
+ else:
48
+ c.execute(query)
49
+ result = c.fetchall() if query.strip().upper().startswith('SELECT') else None
50
+ if commit:
51
+ conn.commit()
52
+ return result
53
+ except sqlite3.OperationalError as e:
54
+ if "database is locked" in str(e) and attempt < self.max_retries - 1:
55
+ time.sleep(self.retry_delay * (2 ** attempt))
56
+ continue
57
+ raise
58
+ raise sqlite3.OperationalError("Query failed after retries")
59
+
60
+ # ================================================================
61
+ # INICIALIZAÇÃO + MIGRAÇÃO AUTOMÁTICA
62
+ # ================================================================
63
+
64
+ def _init_db(self):
65
+ with self._get_connection() as conn:
66
+ c = conn.cursor()
67
+
68
+ # Tabelas principais
69
+ c.executescript('''
70
+ -- aprendizado (simples)
71
+ CREATE TABLE IF NOT EXISTS aprendizado (
72
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
73
+ usuario TEXT,
74
+ dado TEXT,
75
+ valor TEXT
76
+ );
77
+
78
+ -- exemplos
79
+ CREATE TABLE IF NOT EXISTS exemplos (
80
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
81
+ tipo TEXT NOT NULL,
82
+ entrada TEXT NOT NULL,
83
+ resposta TEXT NOT NULL
84
+ );
85
+
86
+ -- info_geral
87
+ CREATE TABLE IF NOT EXISTS info_geral (
88
+ chave TEXT PRIMARY KEY,
89
+ valor TEXT NOT NULL
90
+ );
91
+
92
+ -- estilos
93
+ CREATE TABLE IF NOT EXISTS estilos (
94
+ numero_usuario TEXT PRIMARY KEY,
95
+ estilo TEXT NOT NULL
96
+ );
97
+
98
+ -- preferencias_tom
99
+ CREATE TABLE IF NOT EXISTS preferencias_tom (
100
+ numero_usuario TEXT PRIMARY KEY,
101
+ tom TEXT NOT NULL
102
+ );
103
+
104
+ -- afinidades
105
+ CREATE TABLE IF NOT EXISTS afinidades (
106
+ numero_usuario TEXT PRIMARY KEY,
107
+ afinidade REAL NOT NULL
108
+ );
109
+
110
+ -- termos
111
+ CREATE TABLE IF NOT EXISTS termos (
112
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
113
+ numero_usuario TEXT NOT NULL,
114
+ termo TEXT NOT NULL,
115
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
116
+ );
117
+
118
+ -- aprendizados (detalhado)
119
+ CREATE TABLE IF NOT EXISTS aprendizados (
120
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
121
+ numero_usuario TEXT NOT NULL,
122
+ chave TEXT NOT NULL,
123
+ valor TEXT NOT NULL,
124
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
125
+ );
126
+
127
+ -- vocabulário patenteado
128
+ CREATE TABLE IF NOT EXISTS vocabulario_patenteado (
129
+ termo TEXT PRIMARY KEY,
130
+ definicao TEXT NOT NULL,
131
+ uso TEXT NOT NULL,
132
+ exemplo TEXT NOT NULL
133
+ );
134
+
135
+ -- usuarios privilegiados
136
+ CREATE TABLE IF NOT EXISTS usuarios_privilegiados (
137
+ numero_usuario TEXT PRIMARY KEY,
138
+ nome TEXT NOT NULL
139
+ );
140
+
141
+ -- whatsapp_ids
142
+ CREATE TABLE IF NOT EXISTS whatsapp_ids (
143
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
144
+ whatsapp_id TEXT NOT NULL,
145
+ sender_number TEXT NOT NULL,
146
+ UNIQUE (whatsapp_id, sender_number)
147
+ );
148
+
149
+ -- embeddings
150
+ CREATE TABLE IF NOT EXISTS embeddings (
151
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
152
+ texto TEXT NOT NULL,
153
+ embedding BLOB NOT NULL
154
+ );
155
+
156
+ -- mensagens
157
+ CREATE TABLE IF NOT EXISTS mensagens (
158
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
159
+ usuario TEXT NOT NULL,
160
+ mensagem TEXT NOT NULL,
161
+ resposta TEXT NOT NULL,
162
+ numero TEXT,
163
+ is_reply BOOLEAN DEFAULT 0,
164
+ mensagem_original TEXT,
165
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
166
+ data TIMESTAMP DEFAULT CURRENT_TIMESTAMP
167
+ );
168
+
169
+ -- emocao_exemplos
170
+ CREATE TABLE IF NOT EXISTS emocao_exemplos (
171
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
172
+ emocao TEXT NOT NULL,
173
+ entrada TEXT NOT NULL,
174
+ resposta TEXT NOT NULL,
175
+ tom TEXT NOT NULL,
176
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
177
+ );
178
+
179
+ -- girias_aprendidas
180
+ CREATE TABLE IF NOT EXISTS girias_aprendidas (
181
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
182
+ numero_usuario TEXT NOT NULL,
183
+ giria TEXT NOT NULL,
184
+ significado TEXT NOT NULL,
185
+ contexto TEXT,
186
+ frequencia INTEGER DEFAULT 1,
187
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
188
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
189
+ );
190
+
191
+ -- tom_usuario
192
+ CREATE TABLE IF NOT EXISTS tom_usuario (
193
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
194
+ numero_usuario TEXT NOT NULL,
195
+ tom_detectado TEXT NOT NULL,
196
+ intensidade REAL DEFAULT 0.5,
197
+ contexto TEXT,
198
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
199
+ );
200
+
201
+ -- adaptacao_dinamica
202
+ CREATE TABLE IF NOT EXISTS adaptacao_dinamica (
203
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
204
+ numero_usuario TEXT NOT NULL,
205
+ tipo_adaptacao TEXT NOT NULL,
206
+ valor_anterior TEXT,
207
+ valor_novo TEXT,
208
+ razao TEXT,
209
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
210
+ );
211
+ ''')
212
+
213
+ conn.commit()
214
+
215
+ def _ensure_all_columns_and_indexes(self):
216
+ """Garante que TODAS as colunas existam + cria índices para performance"""
217
+ with self._get_connection() as conn:
218
+ c = conn.cursor()
219
+
220
+ # --- MIGRAÇÃO AUTOMÁTICA DE COLUNAS ---
221
+ migrations = {
222
+ 'mensagens': [
223
+ ("numero", "TEXT"),
224
+ ("is_reply", "BOOLEAN DEFAULT 0"),
225
+ ("mensagem_original", "TEXT"),
226
+ ("created_at", "TIMESTAMP DEFAULT CURRENT_TIMESTAMP"),
227
+ ("data", "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
228
+ ],
229
+ 'girias_aprendidas': [
230
+ ("contexto", "TEXT"),
231
+ ("frequencia", "INTEGER DEFAULT 1"),
232
+ ("updated_at", "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
233
+ ],
234
+ 'tom_usuario': [
235
+ ("intensidade", "REAL DEFAULT 0.5"),
236
+ ("contexto", "TEXT")
237
+ ]
238
+ }
239
+
240
+ for table, cols in migrations.items():
241
+ c.execute(f"PRAGMA table_info('{table}')")
242
+ existing = {row[1] for row in c.fetchall()}
243
+ for col_name, col_def in cols:
244
+ if col_name not in existing:
245
+ try:
246
+ c.execute(f"ALTER TABLE {table} ADD COLUMN {col_name} {col_def}")
247
+ logger.info(f"Coluna '{col_name}' adicionada em '{table}'")
248
+ except Exception as e:
249
+ logger.warning(f"Erro ao adicionar coluna {col_name}: {e}")
250
+
251
+ # --- ÍNDICES PARA PERFORMANCE ---
252
+ indexes = [
253
+ "CREATE INDEX IF NOT EXISTS idx_mensagens_numero ON mensagens(numero);",
254
+ "CREATE INDEX IF NOT EXISTS idx_mensagens_created ON mensagens(created_at DESC);",
255
+ "CREATE INDEX IF NOT EXISTS idx_girias_usuario ON girias_aprendidas(numero_usuario);",
256
+ "CREATE INDEX IF NOT EXISTS idx_girias_giria ON girias_aprendidas(giria);",
257
+ "CREATE INDEX IF NOT EXISTS idx_tom_usuario ON tom_usuario(numero_usuario);",
258
+ "CREATE INDEX IF NOT EXISTS idx_aprendizados_usuario ON aprendizados(numero_usuario);",
259
+ "CREATE INDEX IF NOT EXISTS idx_embeddings_texto ON embeddings(texto);"
260
+ ]
261
+
262
+ for idx in indexes:
263
+ try:
264
+ c.execute(idx)
265
+ except:
266
+ pass
267
+
268
+ conn.commit()
269
+
270
+ # ================================================================
271
+ # MÉTODOS PRINCIPAIS (mantidos e otimizados)
272
+ # ================================================================
273
+
274
+ # --- EXEMPLOS ---
275
+ def inserir_exemplo(self, tipo, entrada, resposta):
276
+ self._execute_with_retry("INSERT INTO exemplos (tipo, entrada, resposta) VALUES (?, ?, ?)", (tipo, entrada, resposta), commit=True)
277
+
278
+ def recuperar_exemplos(self, tipo=None):
279
+ if tipo:
280
+ return self._execute_with_retry("SELECT entrada, resposta FROM exemplos WHERE tipo=? ORDER BY id DESC", (tipo,))
281
+ return self._execute_with_retry("SELECT entrada, resposta FROM exemplos ORDER BY id DESC")
282
+
283
+ # --- INFO_GERAL ---
284
+ def salvar_info_geral(self, chave, valor):
285
+ self._execute_with_retry("INSERT OR REPLACE INTO info_geral (chave, valor) VALUES (?, ?)", (chave, valor), commit=True)
286
+
287
+ def recuperar_info_geral(self, chave):
288
+ result = self._execute_with_retry("SELECT valor FROM info_geral WHERE chave=?", (chave,))
289
+ return result[0][0] if result else None
290
+
291
+ # --- ESTILOS ---
292
+ def salvar_estilo(self, numero_usuario, estilo):
293
+ self._execute_with_retry("INSERT OR REPLACE INTO estilos (numero_usuario, estilo) VALUES (?, ?)", (numero_usuario, estilo), commit=True)
294
+
295
+ def recuperar_estilo(self, numero_usuario):
296
+ result = self._execute_with_retry("SELECT estilo FROM estilos WHERE numero_usuario=?", (numero_usuario,))
297
+ return result[0][0] if result else None
298
+
299
+ # --- PREFERENCIAS_TOM ---
300
+ def salvar_preferencia_tom(self, numero_usuario, tom):
301
+ self._execute_with_retry("INSERT OR REPLACE INTO preferencias_tom (numero_usuario, tom) VALUES (?, ?)", (numero_usuario, tom), commit=True)
302
+
303
+ def recuperar_preferencia_tom(self, numero_usuario):
304
+ result = self._execute_with_retry("SELECT tom FROM preferencias_tom WHERE numero_usuario=?", (numero_usuario,))
305
+ return result[0][0] if result else None
306
+
307
+ # --- AFINIDADES ---
308
+ def salvar_afinidade(self, numero_usuario, afinidade):
309
+ self._execute_with_retry("INSERT OR REPLACE INTO afinidades (numero_usuario, afinidade) VALUES (?, ?)", (numero_usuario, afinidade), commit=True)
310
+
311
+ def recuperar_afinidade(self, numero_usuario):
312
+ result = self._execute_with_retry("SELECT afinidade FROM afinidades WHERE numero_usuario=?", (numero_usuario,))
313
+ return result[0][0] if result else None
314
+
315
+ # --- TERMOS ---
316
+ def registrar_termo(self, numero_usuario, termo):
317
+ self._execute_with_retry("INSERT INTO termos (numero_usuario, termo) VALUES (?, ?)", (numero_usuario, termo), commit=True)
318
+
319
+ def recuperar_termos(self, numero_usuario):
320
+ result = self._execute_with_retry("SELECT termo FROM termos WHERE numero_usuario=? ORDER BY created_at DESC", (numero_usuario,))
321
+ return [row[0] for row in result] if result else []
322
+
323
+ # --- APRENDIZADOS DETALHADOS ---
324
+ def salvar_aprendizado_detalhado(self, numero_usuario, chave, valor):
325
+ self._execute_with_retry("INSERT INTO aprendizados (numero_usuario, chave, valor) VALUES (?, ?, ?)", (numero_usuario, chave, valor), commit=True)
326
+
327
+ def recuperar_aprendizado_detalhado(self, numero_usuario, chave=None):
328
+ if chave:
329
+ result = self._execute_with_retry("SELECT valor FROM aprendizados WHERE numero_usuario=? AND chave=? ORDER BY created_at DESC LIMIT 1", (numero_usuario, chave))
330
+ return result[0][0] if result else None
331
+ return self._execute_with_retry("SELECT chave, valor FROM aprendizados WHERE numero_usuario=? ORDER BY created_at DESC", (numero_usuario,))
332
+
333
+ # --- VOCABULÁRIO PATENTEADO ---
334
+ def registrar_vocabulario_patenteado(self, termo, definicao, uso, exemplo):
335
+ self._execute_with_retry("INSERT OR REPLACE INTO vocabulario_patenteado (termo, definicao, uso, exemplo) VALUES (?, ?, ?, ?)", (termo, definicao, uso, exemplo), commit=True)
336
+
337
+ def recuperar_vocabulario_patenteado(self, termo):
338
+ result = self._execute_with_retry("SELECT definicao, uso, exemplo FROM vocabulario_patenteado WHERE termo=?", (termo,))
339
+ return result[0] if result else None
340
+
341
+ # --- USUÁRIOS PRIVILEGIADOS ---
342
+ def adicionar_usuario_privilegiado(self, numero_usuario, nome):
343
+ self._execute_with_retry("INSERT OR REPLACE INTO usuarios_privilegiados (numero_usuario, nome) VALUES (?, ?)", (numero_usuario, nome), commit=True)
344
+
345
+ def verificar_usuario_privilegiado(self, numero_usuario):
346
+ result = self._execute_with_retry("SELECT nome FROM usuarios_privilegiados WHERE numero_usuario=?", (numero_usuario,))
347
+ return result[0][0] if result else None
348
+
349
+ # --- WHATSAPP IDS ---
350
+ def registrar_whatsapp_id(self, whatsapp_id, sender_number):
351
+ self._execute_with_retry("INSERT OR IGNORE INTO whatsapp_ids (whatsapp_id, sender_number) VALUES (?, ?)", (whatsapp_id, sender_number), commit=True)
352
+
353
+ def recuperar_whatsapp_ids(self, sender_number):
354
+ result = self._execute_with_retry("SELECT whatsapp_id FROM whatsapp_ids WHERE sender_number=?", (sender_number,))
355
+ return [row[0] for row in result] if result else []
356
+
357
+ # --- EMBEDDINGS ---
358
+ def salvar_embedding(self, texto, embedding_bytes):
359
+ self._execute_with_retry("INSERT INTO embeddings (texto, embedding) VALUES (?, ?)", (texto, embedding_bytes), commit=True)
360
+
361
+ def recuperar_embedding(self, texto):
362
+ result = self._execute_with_retry("SELECT embedding FROM embeddings WHERE texto=? ORDER BY id DESC LIMIT 1", (texto,))
363
+ return result[0][0] if result else None
364
+
365
+ # --- MENSAGENS ---
366
+ def salvar_mensagem(self, usuario, mensagem, resposta, numero=None, is_reply=False, mensagem_original=None):
367
+ try:
368
+ cols = ['usuario', 'mensagem', 'resposta']
369
+ vals = [usuario, mensagem, resposta]
370
+ if numero: cols.append('numero'); vals.append(numero)
371
+ if is_reply is not None: cols.append('is_reply'); vals.append(int(is_reply))
372
+ if mensagem_original: cols.append('mensagem_original'); vals.append(mensagem_original)
373
+
374
+ placeholders = ', '.join(['?' for _ in cols])
375
+ query = f"INSERT INTO mensagens ({', '.join(cols)}) VALUES ({placeholders})"
376
+ self._execute_with_retry(query, tuple(vals), commit=True)
377
+ except Exception as e:
378
+ logger.warning(f"Fallback salvar_mensagem: {e}")
379
+ self._execute_with_retry("INSERT INTO mensagens (usuario, mensagem, resposta) VALUES (?, ?, ?)", (usuario, mensagem, resposta), commit=True)
380
+
381
+ def recuperar_mensagens(self, usuario, limite=5):
382
+ return self._execute_with_retry("SELECT mensagem, resposta FROM mensagens WHERE usuario=? OR numero=? ORDER BY id DESC LIMIT ?", (usuario, usuario, limite))
383
+
384
+ # --- APRENDIZADO SIMPLES ---
385
+ def salvar_aprendizado(self, usuario, dado, valor):
386
+ self._execute_with_retry("INSERT INTO aprendizado (usuario, dado, valor) VALUES (?, ?, ?)", (usuario, dado, valor), commit=True)
387
+
388
+ def recuperar_aprendizado(self, usuario, dado):
389
+ result = self._execute_with_retry("SELECT valor FROM aprendizado WHERE usuario=? AND dado=? ORDER BY id DESC LIMIT 1", (usuario, dado))
390
+ return result[0][0] if result else None
391
+
392
+ # --- EMOÇÃO EXEMPLOS ---
393
+ def salvar_exemplo_emocao(self, emocao: str, entrada: str, resposta: str, tom: str):
394
+ self._execute_with_retry("INSERT INTO emocao_exemplos (emocao, entrada, resposta, tom) VALUES (?, ?, ?, ?)", (emocao, entrada, resposta, tom), commit=True)
395
+
396
+ def recuperar_exemplos_emocao(self, emocao: str) -> List[Dict[str, Any]]:
397
+ result = self._execute_with_retry("SELECT entrada, resposta, tom FROM emocao_exemplos WHERE emocao=? ORDER BY created_at DESC", (emocao,))
398
+ return [{"entrada": r[0], "resposta": r[1], "tom": r[2]} for r in result] if result else []
399
+
400
+ # --- GIRIAS APRENDIDAS ---
401
+ def salvar_giria_aprendida(self, numero_usuario: str, giria: str, significado: str, contexto: Optional[str] = None):
402
+ existing = self._execute_with_retry("SELECT id, frequencia FROM girias_aprendidas WHERE numero_usuario=? AND giria=?", (numero_usuario, giria))
403
+ if existing:
404
+ self._execute_with_retry("UPDATE girias_aprendidas SET frequencia=frequencia+1, updated_at=CURRENT_TIMESTAMP WHERE id=?", (existing[0][0],), commit=True)
405
+ else:
406
+ self._execute_with_retry("INSERT INTO girias_aprendidas (numero_usuario, giria, significado, contexto) VALUES (?, ?, ?, ?)", (numero_usuario, giria, significado, contexto), commit=True)
407
+
408
+ def recuperar_girias_usuario(self, numero_usuario: str) -> List[Dict[str, Any]]:
409
+ result = self._execute_with_retry("SELECT giria, significado, contexto, frequencia FROM girias_aprendidas WHERE numero_usuario=? ORDER BY frequencia DESC, updated_at DESC", (numero_usuario,))
410
+ return [{"giria": r[0], "significado": r[1], "contexto": r[2], "frequencia": r[3]} for r in result] if result else []
411
+
412
+ def buscar_significado_giria(self, numero_usuario: str, giria: str) -> Optional[str]:
413
+ result = self._execute_with_retry("SELECT significado FROM girias_aprendidas WHERE numero_usuario=? AND giria=?", (numero_usuario, giria))
414
+ return result[0][0] if result else None
415
+
416
+ # --- TOM USUÁRIO ---
417
+ def registrar_tom_usuario(self, numero_usuario: str, tom_detectado: str, intensidade: float = 0.5, contexto: Optional[str] = None):
418
+ self._execute_with_retry("INSERT INTO tom_usuario (numero_usuario, tom_detectado, intensidade, contexto) VALUES (?, ?, ?, ?)", (numero_usuario, tom_detectado, intensidade, contexto), commit=True)
419
+
420
+ def obter_tom_predominante(self, numero_usuario: str, limite: int = 10) -> Optional[str]:
421
+ result = self._execute_with_retry("""
422
+ SELECT tom_detectado, COUNT(*) FROM tom_usuario
423
+ WHERE numero_usuario=? GROUP BY tom_detectado
424
+ ORDER BY COUNT(*) DESC LIMIT 1
425
+ """, (numero_usuario,))
426
+ return result[0][0] if result else None
427
+
428
+ # --- ADAPTAÇÃO DINÂMICA ---
429
+ def registrar_adaptacao(self, numero_usuario: str, tipo_adaptacao: str, valor_anterior: str, valor_novo: str, razao: str):
430
+ self._execute_with_retry("INSERT INTO adaptacao_dinamica (numero_usuario, tipo_adaptacao, valor_anterior, valor_novo, razao) VALUES (?, ?, ?, ?, ?)", (numero_usuario, tipo_adaptacao, valor_anterior, valor_novo, razao), commit=True)
431
+
432
+ def obter_adaptacoes_usuario(self, numero_usuario: str, tipo_adaptacao: Optional[str] = None) -> List[Dict[str, Any]]:
433
+ if tipo_adaptacao:
434
+ result = self._execute_with_retry("SELECT tipo_adaptacao, valor_anterior, valor_novo, razao, created_at FROM adaptacao_dinamica WHERE numero_usuario=? AND tipo_adaptacao=? ORDER BY created_at DESC", (numero_usuario, tipo_adaptacao))
435
+ else:
436
+ result = self._execute_with_retry("SELECT tipo_adaptacao, valor_anterior, valor_novo, razao, created_at FROM adaptacao_dinamica WHERE numero_usuario=? ORDER BY created_at DESC", (numero_usuario,))
437
+ return [{"tipo": r[0], "anterior": r[1], "novo": r[2], "razao": r[3], "data": r[4]} for r in result] if result else []
438
+
439
+ # --- ANÁLISE DE EMOÇÕES ---
440
+ def analisar_emocoes_mensagem(self, mensagem: str) -> Dict[str, Any]:
441
+ msg = mensagem.lower()
442
+ keywords = {
443
+ "feliz": ["feliz", "alegre", "contente", "ótimo", "bom", "incrível", "show", "top", "fixe", "bué"],
444
+ "triste": ["triste", "chateado", "ruim", "péssimo", "horrível", "deprimido"],
445
+ "raiva": ["raiva", "puto", "irritado", "furioso", "nervoso", "merda", "caralho"],
446
+ "medo": ["medo", "assustado", "preocupado", "ansioso"],
447
+ "surpresa": ["uau", "caramba", "incrível", "surpreso"],
448
+ "nojo": ["nojo", "repugnante", "detesto", "odeio"],
449
+ "amor": ["amo", "adoro", "gosto", "apaixonado"]
450
+ }
451
+ max_count = 0
452
+ emocao = "neutro"
453
+ for e, words in keywords.items():
454
+ count = sum(1 for w in words if w in msg)
455
+ if count > max_count:
456
+ max_count = count
457
+ emocao = e
458
+ return {
459
+ "emocao": emocao,
460
+ "intensidade": min(max_count / 3.0, 1.0),
461
+ "palavras_chave_encontradas": max_count
462
+ }
463
+
464
+ # --- CONTEXTO COMPLETO ---
465
+ def obter_contexto_aprendizado(self, numero_usuario: str) -> Dict[str, Any]:
466
+ return {
467
+ "girias": self.recuperar_girias_usuario(numero_usuario),
468
+ "tom_predominante": self.obter_tom_predominante(numero_usuario),
469
+ "estilo": self.recuperar_estilo(numero_usuario),
470
+ "preferencia_tom": self.recuperar_preferencia_tom(numero_usuario),
471
+ "afinidade": self.recuperar_afinidade(numero_usuario),
472
+ "adaptacoes_recentes": self.obter_adaptacoes_usuario(numero_usuario)[-5:],
473
+ "aprendizados_detalhados": dict(self.recuperar_aprendizado_detalhado(numero_usuario) or [])
474
+ }