akra35567 commited on
Commit
90a3599
·
1 Parent(s): 065eb02

Update modules/database.py

Browse files
Files changed (1) hide show
  1. modules/database.py +237 -146
modules/database.py CHANGED
@@ -1,11 +1,13 @@
1
  """
2
  Banco de dados SQLite para Akira IA.
3
  Gerencia contexto, mensagens, embeddings, gírias, tom e aprendizados detalhados.
4
- Versão 11/2025 - compatível com treinamento.py (SentenceTransformers)
5
  """
 
6
  import sqlite3
7
  import time
8
  import os
 
9
  from typing import Optional, List, Dict, Any, Tuple
10
  from loguru import logger
11
 
@@ -20,116 +22,185 @@ class Database:
20
  self._ensure_all_columns_and_indexes()
21
 
22
  # ================================================================
23
- # CONEXÃO + RETRY
24
  # ================================================================
25
  def _get_connection(self) -> sqlite3.Connection:
26
  for attempt in range(self.max_retries):
27
  try:
28
  conn = sqlite3.connect(self.db_path, timeout=30.0, check_same_thread=False)
29
- conn.execute("PRAGMA journal_mode=WAL")
30
- conn.execute("PRAGMA synchronous=NORMAL")
31
- conn.execute("PRAGMA cache_size=1000")
32
- conn.execute("PRAGMA temp_store=MEMORY")
33
- conn.execute("PRAGMA busy_timeout=30000")
34
- conn.execute("PRAGMA foreign_keys=ON")
35
  return conn
36
  except sqlite3.OperationalError as e:
37
- if "locked" in str(e) and attempt < self.max_retries - 1:
38
  time.sleep(self.retry_delay * (2 ** attempt))
39
  continue
40
- logger.error(f"Erro de conexão DB: {e}")
41
  raise
42
- raise sqlite3.OperationalError("Falha ao conectar ao banco após várias tentativas")
43
 
44
- def _execute_with_retry(self, query: str, params: Optional[tuple] = None, commit: bool = False):
45
  for attempt in range(self.max_retries):
46
  try:
47
  with self._get_connection() as conn:
48
- cur = conn.cursor()
49
- cur.execute(query, params or ())
50
- result = cur.fetchall() if query.strip().upper().startswith("SELECT") else None
 
 
 
51
  if commit:
52
  conn.commit()
53
  return result
54
  except sqlite3.OperationalError as e:
55
- if "locked" in str(e) and attempt < self.max_retries - 1:
56
  time.sleep(self.retry_delay * (2 ** attempt))
57
  continue
58
- logger.error(f"Erro SQL: {e}")
59
  raise
60
  raise sqlite3.OperationalError("Query falhou após retries")
61
 
62
  # ================================================================
63
- # SCHEMA + MIGRAÇÃO
64
  # ================================================================
65
  def _init_db(self):
66
  try:
67
  with self._get_connection() as conn:
68
  c = conn.cursor()
69
- c.executescript("""
70
- CREATE TABLE IF NOT EXISTS mensagens (
71
- id INTEGER PRIMARY KEY AUTOINCREMENT,
72
- usuario TEXT,
73
- mensagem TEXT,
74
- resposta TEXT,
75
- numero TEXT,
76
- is_reply BOOLEAN DEFAULT 0,
77
- mensagem_original TEXT,
78
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
79
- );
80
- CREATE TABLE IF NOT EXISTS embeddings (
81
- id INTEGER PRIMARY KEY AUTOINCREMENT,
82
- numero_usuario TEXT,
83
- source_type TEXT,
84
- texto TEXT,
85
- embedding BLOB
86
- );
87
- CREATE TABLE IF NOT EXISTS aprendizados (
88
- id INTEGER PRIMARY KEY AUTOINCREMENT,
89
- numero_usuario TEXT,
90
- chave TEXT,
91
- valor TEXT,
92
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
93
- );
94
- CREATE TABLE IF NOT EXISTS girias_aprendidas (
95
- id INTEGER PRIMARY KEY AUTOINCREMENT,
96
- numero_usuario TEXT,
97
- giria TEXT,
98
- significado TEXT,
99
- contexto TEXT,
100
- frequencia INTEGER DEFAULT 1,
101
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
102
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
103
- );
104
- CREATE TABLE IF NOT EXISTS tom_usuario (
105
- id INTEGER PRIMARY KEY AUTOINCREMENT,
106
- numero_usuario TEXT,
107
- tom_detectado TEXT,
108
- intensidade REAL DEFAULT 0.5,
109
- contexto TEXT,
110
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
111
- );
112
- CREATE TABLE IF NOT EXISTS contexto (
113
- user_key TEXT PRIMARY KEY,
114
- historico TEXT,
115
- emocao_atual TEXT,
116
- termos TEXT,
117
- girias TEXT,
118
- tom TEXT
119
- );
120
- CREATE TABLE IF NOT EXISTS pronomes_por_tom (
121
- tom TEXT PRIMARY KEY,
122
- pronomes TEXT
123
- );
124
- """);
125
-
126
- # Dados padrão
127
- c.execute("INSERT OR IGNORE INTO pronomes_por_tom (tom, pronomes) VALUES (?, ?)", ('neutro', 'tu/você'))
128
- c.execute("INSERT OR IGNORE INTO pronomes_por_tom (tom, pronomes) VALUES (?, ?)", ('formal', 'o senhor/a senhora'))
129
- c.execute("INSERT OR IGNORE INTO pronomes_por_tom (tom, pronomes) VALUES (?, ?)", ('informal', 'puto/kota'))
130
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  conn.commit()
132
- logger.info(f"Banco inicializado: {self.db_path}")
133
  except Exception as e:
134
  logger.error(f"Erro ao criar tabelas: {e}")
135
  raise
@@ -139,16 +210,17 @@ class Database:
139
  with self._get_connection() as conn:
140
  c = conn.cursor()
141
  migrations = {
142
- 'embeddings': [
143
- ("numero_usuario", "TEXT"),
144
- ("source_type", "TEXT")
145
- ],
146
  'mensagens': [
147
  ("numero", "TEXT"),
148
  ("is_reply", "BOOLEAN DEFAULT 0"),
149
  ("mensagem_original", "TEXT"),
150
  ("created_at", "DATETIME DEFAULT CURRENT_TIMESTAMP")
151
  ],
 
 
 
 
 
152
  'tom_usuario': [
153
  ("intensidade", "REAL DEFAULT 0.5"),
154
  ("contexto", "TEXT")
@@ -159,6 +231,11 @@ class Database:
159
  ("termos", "TEXT"),
160
  ("girias", "TEXT"),
161
  ("tom", "TEXT")
 
 
 
 
 
162
  ]
163
  }
164
  for table, cols in migrations.items():
@@ -171,12 +248,28 @@ class Database:
171
  logger.info(f"Coluna '{col_name}' adicionada em '{table}'")
172
  except Exception as e:
173
  logger.warning(f"Erro ao adicionar coluna {col_name}: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  conn.commit()
175
  except Exception as e:
176
- logger.error(f"Erro na migração: {e}")
177
 
178
  # ================================================================
179
- # FUNÇÕES PRINCIPAIS
180
  # ================================================================
181
  def salvar_mensagem(self, usuario, mensagem, resposta, numero=None, is_reply=False, mensagem_original=None):
182
  try:
@@ -195,31 +288,34 @@ class Database:
195
  query = f"INSERT INTO mensagens ({', '.join(cols)}) VALUES ({placeholders})"
196
  self._execute_with_retry(query, tuple(vals), commit=True)
197
  except Exception as e:
198
- logger.warning(f"Erro salvar_mensagem: {e}")
199
  self._execute_with_retry(
200
  "INSERT INTO mensagens (usuario, mensagem, resposta) VALUES (?, ?, ?)",
201
  (usuario, mensagem, resposta),
202
  commit=True
203
  )
204
 
205
- def recuperar_mensagens(self, usuario: str, limite: int = 5):
206
  return self._execute_with_retry(
207
  "SELECT mensagem, resposta FROM mensagens WHERE usuario=? OR numero=? ORDER BY id DESC LIMIT ?",
208
  (usuario, usuario, limite)
209
  ) or []
210
 
211
- def salvar_embedding(self, numero_usuario: str, source_type: str, texto: str, embedding):
 
212
  """Compatível com paraphrase-MiniLM e numpy arrays."""
213
  try:
214
  if hasattr(embedding, "tobytes"):
215
  embedding = embedding.tobytes()
 
216
  self._execute_with_retry(
217
  "INSERT INTO embeddings (numero_usuario, source_type, texto, embedding) VALUES (?, ?, ?, ?)",
218
  (numero_usuario, source_type, texto, embedding),
219
  commit=True
220
  )
221
  except Exception as e:
222
- logger.warning(f"Erro salvar_embedding: {e}")
 
223
  self._execute_with_retry(
224
  "INSERT INTO embeddings (texto, embedding) VALUES (?, ?)",
225
  (texto, embedding.tobytes() if hasattr(embedding, "tobytes") else embedding),
@@ -229,46 +325,60 @@ class Database:
229
  # ================================================================
230
  # CONTEXTO / TOM / GÍRIAS / APRENDIZADOS
231
  # ================================================================
232
- def salvar_contexto(self, user_key, historico, emocao_atual, termos, girias, tom):
233
- self._execute_with_retry(
234
- """INSERT OR REPLACE INTO contexto
235
- (user_key, historico, emocao_atual, termos, girias, tom)
236
- VALUES (?, ?, ?, ?, ?, ?)""",
237
- (user_key, historico, emocao_atual, termos, girias, tom),
238
- commit=True
239
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
 
241
- def registrar_tom_usuario(self, numero_usuario, tom_detectado, intensidade=0.5, contexto=None):
242
- self._execute_with_retry(
243
- "INSERT INTO tom_usuario (numero_usuario, tom_detectado, intensidade, contexto) VALUES (?, ?, ?, ?)",
244
- (numero_usuario, tom_detectado, intensidade, contexto),
245
- commit=True
246
- )
247
 
248
- def obter_tom_predominante(self, numero_usuario):
249
  rows = self._execute_with_retry(
250
  "SELECT tom_detectado FROM tom_usuario WHERE numero_usuario=? ORDER BY created_at DESC LIMIT 1",
251
  (numero_usuario,)
252
- )
253
  return rows[0][0] if rows else None
254
 
255
- def salvar_aprendizado_detalhado(self, numero_usuario, chave, valor):
256
  self._execute_with_retry(
257
- "INSERT INTO aprendizados (numero_usuario, chave, valor) VALUES (?, ?, ?)",
258
- (numero_usuario, chave, valor),
259
  commit=True
260
  )
261
 
262
- def recuperar_aprendizado_detalhado(self, numero_usuario, *args):
263
- rows = self._execute_with_retry(
264
- "SELECT chave, valor FROM aprendizados WHERE numero_usuario=?",
265
- (numero_usuario,)
 
266
  )
267
- return {r[0]: r[1] for r in rows} if rows else {}
268
 
269
- def salvar_giria_aprendida(self, numero_usuario, giria, significado, contexto=None):
270
  existing = self._execute_with_retry(
271
- "SELECT id FROM girias_aprendidas WHERE numero_usuario=? AND giria=?",
272
  (numero_usuario, giria)
273
  )
274
  if existing:
@@ -284,32 +394,13 @@ class Database:
284
  commit=True
285
  )
286
 
287
- # ================================================================
288
- # MÉTODO ADICIONADO: recuperar_girias_usuario
289
- # ================================================================
290
- def recuperar_girias_usuario(self, numero_usuario: str) -> List[Dict[str, Any]]:
291
- """
292
- Recupera todas as gírias aprendidas para um usuário.
293
- Retorna: [{'giria': 'baza', 'significado': 'ir embora', 'frequencia': 5}, ...]
294
- """
295
- try:
296
- rows = self._execute_with_retry(
297
- """
298
- SELECT giria, significado, frequencia, contexto
299
- FROM girias_aprendidas
300
- WHERE numero_usuario = ?
301
- ORDER BY frequencia DESC, updated_at DESC
302
- """,
303
- (numero_usuario,)
304
- )
305
- return [
306
- {
307
- 'giria': row[0],
308
- 'significado': row[1],
309
- 'frequencia': row[2],
310
- 'contexto': row[3]
311
- } for row in rows
312
- ] if rows else []
313
- except Exception as e:
314
- logger.warning(f"Falha ao carregar gírias do usuário {numero_usuario}: {e}")
315
- return []
 
1
  """
2
  Banco de dados SQLite para Akira IA.
3
  Gerencia contexto, mensagens, embeddings, gírias, tom e aprendizados detalhados.
4
+ Versão completa 11/2025.
5
  """
6
+
7
  import sqlite3
8
  import time
9
  import os
10
+ import json
11
  from typing import Optional, List, Dict, Any, Tuple
12
  from loguru import logger
13
 
 
22
  self._ensure_all_columns_and_indexes()
23
 
24
  # ================================================================
25
+ # CONEXÃO COM RETRY + WAL
26
  # ================================================================
27
  def _get_connection(self) -> sqlite3.Connection:
28
  for attempt in range(self.max_retries):
29
  try:
30
  conn = sqlite3.connect(self.db_path, timeout=30.0, check_same_thread=False)
31
+ conn.execute('PRAGMA journal_mode=WAL')
32
+ conn.execute('PRAGMA synchronous=NORMAL')
33
+ conn.execute('PRAGMA cache_size=1000')
34
+ conn.execute('PRAGMA temp_store=MEMORY')
35
+ conn.execute('PRAGMA busy_timeout=30000')
36
+ conn.execute('PRAGMA foreign_keys=ON')
37
  return conn
38
  except sqlite3.OperationalError as e:
39
+ if "database is locked" in str(e) and attempt < self.max_retries - 1:
40
  time.sleep(self.retry_delay * (2 ** attempt))
41
  continue
42
+ logger.error(f"Falha ao conectar ao banco: {e}")
43
  raise
44
+ raise sqlite3.OperationalError("Falha ao conectar após retries")
45
 
46
+ def _execute_with_retry(self, query: str, params: Optional[tuple] = None, commit: bool = False) -> Optional[List[Tuple]]:
47
  for attempt in range(self.max_retries):
48
  try:
49
  with self._get_connection() as conn:
50
+ c = conn.cursor()
51
+ if params:
52
+ c.execute(query, params)
53
+ else:
54
+ c.execute(query)
55
+ result = c.fetchall() if query.strip().upper().startswith('SELECT') else None
56
  if commit:
57
  conn.commit()
58
  return result
59
  except sqlite3.OperationalError as e:
60
+ if "database is locked" in str(e) and attempt < self.max_retries - 1:
61
  time.sleep(self.retry_delay * (2 ** attempt))
62
  continue
63
+ logger.error(f"Erro SQL (tentativa {attempt+1}): {e}")
64
  raise
65
  raise sqlite3.OperationalError("Query falhou após retries")
66
 
67
  # ================================================================
68
+ # INICIALIZAÇÃO + MIGRAÇÃO AUTOMÁTICA
69
  # ================================================================
70
  def _init_db(self):
71
  try:
72
  with self._get_connection() as conn:
73
  c = conn.cursor()
74
+ c.executescript('''
75
+ CREATE TABLE IF NOT EXISTS aprendizado (
76
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
77
+ usuario TEXT,
78
+ dado TEXT,
79
+ valor TEXT
80
+ );
81
+ CREATE TABLE IF NOT EXISTS exemplos (
82
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
83
+ tipo TEXT NOT NULL,
84
+ entrada TEXT NOT NULL,
85
+ resposta TEXT NOT NULL
86
+ );
87
+ CREATE TABLE IF NOT EXISTS info_geral (
88
+ chave TEXT PRIMARY KEY,
89
+ valor TEXT NOT NULL
90
+ );
91
+ CREATE TABLE IF NOT EXISTS estilos (
92
+ numero_usuario TEXT PRIMARY KEY,
93
+ estilo TEXT NOT NULL
94
+ );
95
+ CREATE TABLE IF NOT EXISTS preferencias_tom (
96
+ numero_usuario TEXT PRIMARY KEY,
97
+ tom TEXT NOT NULL
98
+ );
99
+ CREATE TABLE IF NOT EXISTS afinidades (
100
+ numero_usuario TEXT PRIMARY KEY,
101
+ afinidade REAL NOT NULL
102
+ );
103
+ CREATE TABLE IF NOT EXISTS termos (
104
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
105
+ numero_usuario TEXT NOT NULL,
106
+ termo TEXT NOT NULL,
107
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
108
+ );
109
+ CREATE TABLE IF NOT EXISTS aprendizados (
110
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
111
+ numero_usuario TEXT NOT NULL,
112
+ chave TEXT NOT NULL,
113
+ valor TEXT NOT NULL,
114
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
115
+ );
116
+ CREATE TABLE IF NOT EXISTS vocabulario_patenteado (
117
+ termo TEXT PRIMARY KEY,
118
+ definicao TEXT NOT NULL,
119
+ uso TEXT NOT NULL,
120
+ exemplo TEXT NOT NULL
121
+ );
122
+ CREATE TABLE IF NOT EXISTS usuarios_privilegiados (
123
+ numero_usuario TEXT PRIMARY KEY,
124
+ nome TEXT NOT NULL
125
+ );
126
+ CREATE TABLE IF NOT EXISTS whatsapp_ids (
127
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
128
+ whatsapp_id TEXT NOT NULL,
129
+ sender_number TEXT NOT NULL,
130
+ UNIQUE (whatsapp_id, sender_number)
131
+ );
132
+ CREATE TABLE IF NOT EXISTS embeddings (
133
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
134
+ texto TEXT NOT NULL,
135
+ embedding BLOB NOT NULL
136
+ );
137
+ CREATE TABLE IF NOT EXISTS mensagens (
138
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
139
+ usuario TEXT NOT NULL,
140
+ mensagem TEXT NOT NULL,
141
+ resposta TEXT NOT NULL,
142
+ numero TEXT,
143
+ is_reply BOOLEAN DEFAULT 0,
144
+ mensagem_original TEXT,
145
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
146
+ );
147
+ CREATE TABLE IF NOT EXISTS emocao_exemplos (
148
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
149
+ emocao TEXT NOT NULL,
150
+ entrada TEXT NOT NULL,
151
+ resposta TEXT NOT NULL,
152
+ tom TEXT NOT NULL,
153
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
154
+ );
155
+ CREATE TABLE IF NOT EXISTS girias_aprendidas (
156
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
157
+ numero_usuario TEXT NOT NULL,
158
+ giria TEXT NOT NULL,
159
+ significado TEXT NOT NULL,
160
+ contexto TEXT,
161
+ frequencia INTEGER DEFAULT 1,
162
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
163
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
164
+ );
165
+ CREATE TABLE IF NOT EXISTS tom_usuario (
166
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
167
+ numero_usuario TEXT NOT NULL,
168
+ tom_detectado TEXT NOT NULL,
169
+ intensidade REAL DEFAULT 0.5,
170
+ contexto TEXT,
171
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
172
+ );
173
+ CREATE TABLE IF NOT EXISTS adaptacao_dinamica (
174
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
175
+ numero_usuario TEXT NOT NULL,
176
+ tipo_adaptacao TEXT NOT NULL,
177
+ valor_anterior TEXT,
178
+ valor_novo TEXT,
179
+ razao TEXT,
180
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
181
+ );
182
+ CREATE TABLE IF NOT EXISTS pronomes_por_tom (
183
+ tom TEXT PRIMARY KEY,
184
+ pronomes TEXT NOT NULL
185
+ );
186
+ CREATE TABLE IF NOT EXISTS contexto (
187
+ user_key TEXT PRIMARY KEY,
188
+ historico TEXT,
189
+ emocao_atual TEXT,
190
+ termos TEXT,
191
+ girias TEXT,
192
+ tom TEXT
193
+ );
194
+ ''')
195
+ c.executescript('''
196
+ INSERT OR IGNORE INTO pronomes_por_tom (tom, pronomes) VALUES
197
+ ('formal', 'Sr., ilustre, boss, maior, homem'),
198
+ ('rude', 'parvo, estúpido, burro, analfabeto, desperdício de esperma'),
199
+ ('casual', 'mano, puto, cota, mwangolé, kota'),
200
+ ('neutro', 'amigo, parceiro, camarada');
201
+ ''')
202
  conn.commit()
203
+ logger.info(f"Banco inicializado: {self.db_path}")
204
  except Exception as e:
205
  logger.error(f"Erro ao criar tabelas: {e}")
206
  raise
 
210
  with self._get_connection() as conn:
211
  c = conn.cursor()
212
  migrations = {
 
 
 
 
213
  'mensagens': [
214
  ("numero", "TEXT"),
215
  ("is_reply", "BOOLEAN DEFAULT 0"),
216
  ("mensagem_original", "TEXT"),
217
  ("created_at", "DATETIME DEFAULT CURRENT_TIMESTAMP")
218
  ],
219
+ 'girias_aprendidas': [
220
+ ("contexto", "TEXT"),
221
+ ("frequencia", "INTEGER DEFAULT 1"),
222
+ ("updated_at", "DATETIME DEFAULT CURRENT_TIMESTAMP")
223
+ ],
224
  'tom_usuario': [
225
  ("intensidade", "REAL DEFAULT 0.5"),
226
  ("contexto", "TEXT")
 
231
  ("termos", "TEXT"),
232
  ("girias", "TEXT"),
233
  ("tom", "TEXT")
234
+ ],
235
+ # CORREÇÃO: Adiciona as colunas que faltavam em 'embeddings'
236
+ 'embeddings': [
237
+ ("numero_usuario", "TEXT"),
238
+ ("source_type", "TEXT")
239
  ]
240
  }
241
  for table, cols in migrations.items():
 
248
  logger.info(f"Coluna '{col_name}' adicionada em '{table}'")
249
  except Exception as e:
250
  logger.warning(f"Erro ao adicionar coluna {col_name}: {e}")
251
+ indexes = [
252
+ "CREATE INDEX IF NOT EXISTS idx_mensagens_numero ON mensagens(numero);",
253
+ "CREATE INDEX IF NOT EXISTS idx_mensagens_created ON mensagens(created_at DESC);",
254
+ "CREATE INDEX IF NOT EXISTS idx_girias_usuario ON girias_aprendidas(numero_usuario);",
255
+ "CREATE INDEX IF NOT EXISTS idx_girias_giria ON girias_aprendidas(giria);",
256
+ "CREATE INDEX IF NOT EXISTS idx_tom_usuario ON tom_usuario(numero_usuario);",
257
+ "CREATE INDEX IF NOT EXISTS idx_aprendizados_usuario ON aprendizados(numero_usuario);",
258
+ "CREATE INDEX IF NOT EXISTS idx_embeddings_texto ON embeddings(texto);",
259
+ "CREATE INDEX IF NOT EXISTS idx_pronomes_tom ON pronomes_por_tom(tom);",
260
+ "CREATE INDEX IF NOT EXISTS idx_contexto_user ON contexto(user_key);"
261
+ ]
262
+ for idx in indexes:
263
+ try:
264
+ c.execute(idx)
265
+ except:
266
+ pass
267
  conn.commit()
268
  except Exception as e:
269
+ logger.error(f"Erro na migração/índices: {e}")
270
 
271
  # ================================================================
272
+ # MÉTODOS PRINCIPAIS
273
  # ================================================================
274
  def salvar_mensagem(self, usuario, mensagem, resposta, numero=None, is_reply=False, mensagem_original=None):
275
  try:
 
288
  query = f"INSERT INTO mensagens ({', '.join(cols)}) VALUES ({placeholders})"
289
  self._execute_with_retry(query, tuple(vals), commit=True)
290
  except Exception as e:
291
+ logger.warning(f"Fallback salvar_mensagem: {e}")
292
  self._execute_with_retry(
293
  "INSERT INTO mensagens (usuario, mensagem, resposta) VALUES (?, ?, ?)",
294
  (usuario, mensagem, resposta),
295
  commit=True
296
  )
297
 
298
+ def recuperar_mensagens(self, usuario: str, limite: int = 5) -> List[Tuple]:
299
  return self._execute_with_retry(
300
  "SELECT mensagem, resposta FROM mensagens WHERE usuario=? OR numero=? ORDER BY id DESC LIMIT ?",
301
  (usuario, usuario, limite)
302
  ) or []
303
 
304
+ # CORREÇÃO: Assinatura de 5 argumentos (self + 4) para corresponder ao erro do log
305
+ def salvar_embedding(self, numero_usuario: str, source_type: str, texto: str, embedding: bytes):
306
  """Compatível com paraphrase-MiniLM e numpy arrays."""
307
  try:
308
  if hasattr(embedding, "tobytes"):
309
  embedding = embedding.tobytes()
310
+ # Inserindo com as novas colunas
311
  self._execute_with_retry(
312
  "INSERT INTO embeddings (numero_usuario, source_type, texto, embedding) VALUES (?, ?, ?, ?)",
313
  (numero_usuario, source_type, texto, embedding),
314
  commit=True
315
  )
316
  except Exception as e:
317
+ logger.warning(f"Erro ao salvar embedding (tentativa com 4 args): {e}. Tentando com 2 argumentos (texto, embedding).")
318
+ # Fallback para schema antigo, caso as colunas ainda não tenham migrado
319
  self._execute_with_retry(
320
  "INSERT INTO embeddings (texto, embedding) VALUES (?, ?)",
321
  (texto, embedding.tobytes() if hasattr(embedding, "tobytes") else embedding),
 
325
  # ================================================================
326
  # CONTEXTO / TOM / GÍRIAS / APRENDIZADOS
327
  # ================================================================
328
+
329
+ # CORREÇÃO: Método adicionado para resolver o erro "'Database' object has no attribute 'salvar_contexto'"
330
+ def salvar_contexto(self, user_key: str, historico: str, emocao_atual: str, termos: str, girias: str, tom: str):
331
+ try:
332
+ self._execute_with_retry(
333
+ """INSERT OR REPLACE INTO contexto
334
+ (user_key, historico, emocao_atual, termos, girias, tom)
335
+ VALUES (?, ?, ?, ?, ?, ?)""",
336
+ (user_key, historico, emocao_atual, termos, girias, tom),
337
+ commit=True
338
+ )
339
+ except Exception as e:
340
+ logger.error(f"Erro ao salvar contexto para {user_key}: {e}")
341
+
342
+ # CORREÇÃO: Aceita *args para ignorar o argumento extra (resolve "takes 2 positional arguments but 3 were given")
343
+ def recuperar_aprendizado_detalhado(self, numero_usuario: str, *args) -> Dict[str, str]:
344
+ # O argumento 'chave' (em *args) é ignorado aqui, pois a query busca todas as chaves
345
+ rows = self._execute_with_retry(
346
+ "SELECT chave, valor FROM aprendizados WHERE numero_usuario=?",
347
+ (numero_usuario,)
348
+ ) or []
349
+ return {r[0]: r[1] for r in rows}
350
 
351
+ def recuperar_girias_usuario(self, numero_usuario: str) -> List[Dict[str, Any]]:
352
+ rows = self._execute_with_retry(
353
+ "SELECT giria, significado, contexto, frequencia FROM girias_aprendidas WHERE numero_usuario=?",
354
+ (numero_usuario,)
355
+ ) or []
356
+ return [{'giria': r[0], 'significado': r[1], 'contexto': r[2], 'frequencia': r[3]} for r in rows]
357
 
358
+ def obter_tom_predominante(self, numero_usuario: str) -> Optional[str]:
359
  rows = self._execute_with_retry(
360
  "SELECT tom_detectado FROM tom_usuario WHERE numero_usuario=? ORDER BY created_at DESC LIMIT 1",
361
  (numero_usuario,)
362
+ ) or []
363
  return rows[0][0] if rows else None
364
 
365
+ def registrar_tom_usuario(self, numero_usuario: str, tom_detectado: str, intensidade: float = 0.5, contexto: Optional[str] = None):
366
  self._execute_with_retry(
367
+ "INSERT INTO tom_usuario (numero_usuario, tom_detectado, intensidade, contexto) VALUES (?, ?, ?, ?)",
368
+ (numero_usuario, tom_detectado, intensidade, contexto),
369
  commit=True
370
  )
371
 
372
+ def salvar_aprendizado_detalhado(self, numero_usuario: str, chave: str, valor: str):
373
+ self._execute_with_retry(
374
+ "INSERT OR REPLACE INTO aprendizados (numero_usuario, chave, valor) VALUES (?, ?, ?)",
375
+ (numero_usuario, chave, valor),
376
+ commit=True
377
  )
 
378
 
379
+ def salvar_giria_aprendida(self, numero_usuario: str, giria: str, significado: str, contexto: Optional[str] = None):
380
  existing = self._execute_with_retry(
381
+ "SELECT id, frequencia FROM girias_aprendidas WHERE numero_usuario=? AND giria=?",
382
  (numero_usuario, giria)
383
  )
384
  if existing:
 
394
  commit=True
395
  )
396
 
397
+ def salvar_info_geral(self, chave: str, valor: str):
398
+ self._execute_with_retry(
399
+ "INSERT OR REPLACE INTO info_geral (chave, valor) VALUES (?, ?)",
400
+ (chave, valor),
401
+ commit=True
402
+ )
403
+
404
+ def obter_info_geral(self, chave: str) -> Optional[str]:
405
+ result = self._execute_with_retry("SELECT valor FROM info_geral WHERE chave=?", (chave,))
406
+ return result[0][0] if result else None