akra35567 commited on
Commit
3601233
·
verified ·
1 Parent(s): e54c208

Update modules/database.py

Browse files
Files changed (1) hide show
  1. modules/database.py +190 -161
modules/database.py CHANGED
@@ -7,7 +7,9 @@ Banco de dados SQLite para Akira IA - COMPLETAMENTE ATUALIZADO
7
  ✅ Transições graduais de humor (3 níveis)
8
  ✅ Memória emocional BERT GoEmotions
9
  ✅ Backup e otimização automática
 
10
  """
 
11
  import sqlite3
12
  import time
13
  import os
@@ -25,22 +27,22 @@ class Database:
25
  self.db_path = db_path or config.DB_PATH
26
  self.max_retries = 5
27
  self.retry_delay = 0.1
28
-
29
  # Cria diretório se necessário
30
  db_dir = os.path.dirname(self.db_path)
31
  if db_dir:
32
  os.makedirs(db_dir, exist_ok=True)
33
-
34
  self._init_db()
35
  self._ensure_all_columns_and_indexes()
36
  self._sincronizar_usuarios_privilegiados() # Sincroniza com config
37
-
38
  logger.info(f"✅ Banco V21 inicializado: {self.db_path}")
39
 
40
  # ================================================================
41
  # ISOLAMENTO CRIPTOGRÁFICO (V21 MELHORADO)
42
  # ================================================================
43
-
44
  def _gerar_contexto_id(self, numero: str, tipo: str = 'auto') -> str:
45
  """
46
  Gera ID único isolado com salt dinâmico.
@@ -51,11 +53,11 @@ class Database:
51
  tipo = "grupo"
52
  else:
53
  tipo = "pv"
54
-
55
  # Salt dinâmico baseado em data
56
  data_semana = datetime.now().strftime("%Y-%W")
57
  salt = f"AKIRA_V21_{data_semana}_ISOLATION"
58
-
59
  # Hash único
60
  raw = f"{str(numero).strip()}|{tipo}|{salt}"
61
  return hashlib.sha256(raw.encode()).hexdigest()[:32]
@@ -63,7 +65,7 @@ class Database:
63
  # ================================================================
64
  # CONEXÃO COM OTIMIZAÇÕES
65
  # ================================================================
66
-
67
  def _get_connection(self) -> sqlite3.Connection:
68
  for attempt in range(self.max_retries):
69
  try:
@@ -84,7 +86,7 @@ class Database:
84
  raise
85
  raise sqlite3.OperationalError("Falha após retries")
86
 
87
- def _execute_with_retry(self, query: str, params: Optional[tuple] = None,
88
  commit: bool = False, fetch: bool = True):
89
  for attempt in range(self.max_retries):
90
  try:
@@ -94,10 +96,10 @@ class Database:
94
  c.execute(query, params)
95
  else:
96
  c.execute(query)
97
-
98
  if commit:
99
  conn.commit()
100
-
101
  if fetch and query.strip().upper().startswith('SELECT'):
102
  return c.fetchall()
103
  elif fetch:
@@ -117,13 +119,13 @@ class Database:
117
  # ================================================================
118
  # VERIFICAÇÃO E CRIAÇÃO DE COLUNAS/ÍNDICES
119
  # ================================================================
120
-
121
  def _ensure_all_columns_and_indexes(self):
122
  """Garante que todas as colunas e índices existam"""
123
  try:
124
  with self._get_connection() as conn:
125
  c = conn.cursor()
126
-
127
  # Índices para performance
128
  indexes = [
129
  "CREATE INDEX IF NOT EXISTS idx_mensagens_numero ON mensagens(numero, deletado)",
@@ -137,28 +139,28 @@ class Database:
137
  "CREATE INDEX IF NOT EXISTS idx_comandos_numero ON comandos_executados(numero, timestamp)",
138
  "CREATE INDEX IF NOT EXISTS idx_reset_numero ON reset_log(numero, timestamp)"
139
  ]
140
-
141
  for idx_query in indexes:
142
  try:
143
  c.execute(idx_query)
144
  except Exception as e:
145
  logger.warning(f"Erro ao criar índice: {e}")
146
-
147
  conn.commit()
148
  logger.info("✅ Índices verificados/criados")
149
-
150
  except Exception as e:
151
  logger.error(f"Erro ao verificar colunas/índices: {e}")
152
 
153
  # ================================================================
154
  # INICIALIZAÇÃO COMPLETA
155
  # ================================================================
156
-
157
  def _init_db(self):
158
  try:
159
  with self._get_connection() as conn:
160
  c = conn.cursor()
161
-
162
  # TABELA PRINCIPAL DE MENSAGENS
163
  c.execute('''
164
  CREATE TABLE IF NOT EXISTS mensagens (
@@ -180,7 +182,7 @@ class Database:
180
  deletado BOOLEAN DEFAULT 0
181
  )
182
  ''')
183
-
184
  # TABELA DE USUÁRIOS PRIVILEGIADOS (V21)
185
  c.execute('''
186
  CREATE TABLE IF NOT EXISTS usuarios_privilegiados (
@@ -200,7 +202,7 @@ class Database:
200
  data_atualizacao DATETIME DEFAULT CURRENT_TIMESTAMP
201
  )
202
  ''')
203
-
204
  # TABELA DE CONTEXTO
205
  c.execute('''
206
  CREATE TABLE IF NOT EXISTS contexto (
@@ -222,7 +224,7 @@ class Database:
222
  data_atualizacao DATETIME DEFAULT CURRENT_TIMESTAMP
223
  )
224
  ''')
225
-
226
  # TABELA DE TRANSIÇÕES DE HUMOR
227
  c.execute('''
228
  CREATE TABLE IF NOT EXISTS transicoes_humor (
@@ -239,7 +241,7 @@ class Database:
239
  timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
240
  )
241
  ''')
242
-
243
  # TABELA DE EXEMPLOS DE TREINAMENTO
244
  c.execute('''
245
  CREATE TABLE IF NOT EXISTS training_examples (
@@ -254,7 +256,7 @@ class Database:
254
  timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
255
  )
256
  ''')
257
-
258
  # TABELA DE GÍRIAS APRENDIDAS
259
  c.execute('''
260
  CREATE TABLE IF NOT EXISTS girias_aprendidas (
@@ -269,7 +271,7 @@ class Database:
269
  data_criacao DATETIME DEFAULT CURRENT_TIMESTAMP
270
  )
271
  ''')
272
-
273
  # TABELA DE COMANDOS EXECUTADOS
274
  c.execute('''
275
  CREATE TABLE IF NOT EXISTS comandos_executados (
@@ -282,7 +284,7 @@ class Database:
282
  timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
283
  )
284
  ''')
285
-
286
  # TABELA DE RESETS (LOG)
287
  c.execute('''
288
  CREATE TABLE IF NOT EXISTS reset_log (
@@ -295,11 +297,11 @@ class Database:
295
  timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
296
  )
297
  ''')
298
-
299
  conn.commit()
300
-
301
  logger.info("✅ Todas as tabelas criadas/verificadas")
302
-
303
  except Exception as e:
304
  logger.error(f"❌ Erro ao criar tabelas: {e}")
305
  raise
@@ -307,15 +309,15 @@ class Database:
307
  # ================================================================
308
  # SINCRONIZAÇÃO DE USUÁRIOS PRIVILEGIADOS
309
  # ================================================================
310
-
311
  def _sincronizar_usuarios_privilegiados(self):
312
  """Sincroniza usuários privilegiados do config.py com o banco"""
313
  try:
314
  for numero, dados in config.USUARIOS_PRIVILEGIADOS.items():
315
  self._execute_with_retry(
316
  """
317
- INSERT OR REPLACE INTO usuarios_privilegiados
318
- (numero, nome, nome_curto, tom_inicial, pode_dar_ordens,
319
  pode_usar_reset, pode_forcar_modo, nivel_acesso, data_atualizacao)
320
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
321
  """,
@@ -332,18 +334,17 @@ class Database:
332
  commit=True,
333
  fetch=False
334
  )
335
-
336
  logger.info(f"✅ Usuários privilegiados sincronizados: {len(config.USUARIOS_PRIVILEGIADOS)}")
337
-
338
  except Exception as e:
339
  logger.error(f"Erro ao sincronizar usuários privilegiados: {e}")
340
 
341
  # ================================================================
342
  # VERIFICAÇÃO DE PRIVILÉGIOS (ROBUSTA)
343
  # ================================================================
344
-
345
  def is_usuario_privilegiado(self, numero: str) -> bool:
346
- """Verifica se usuário é privilegiado"""
347
  try:
348
  result = self._execute_with_retry(
349
  "SELECT 1 FROM usuarios_privilegiados WHERE numero = ?",
@@ -354,22 +355,21 @@ class Database:
354
  except Exception as e:
355
  logger.error(f"Erro ao verificar privilégio: {e}")
356
  return False
357
-
358
  def get_usuario_privilegiado(self, numero: str) -> Optional[Dict]:
359
- """Retorna dados completos do usuário privilegiado"""
360
  try:
361
  result = self._execute_with_retry(
362
  """
363
  SELECT numero, nome, nome_curto, tom_inicial, pode_dar_ordens,
364
  pode_usar_reset, pode_forcar_modo, nivel_acesso,
365
  ultimo_comando, timestamp_comando, comandos_executados
366
- FROM usuarios_privilegiados
367
  WHERE numero = ?
368
  """,
369
  (str(numero).strip(),),
370
  fetch=True
371
  )
372
-
373
  if result:
374
  row = result[0]
375
  return {
@@ -386,34 +386,30 @@ class Database:
386
  "comandos_executados": row[10]
387
  }
388
  return None
389
-
390
  except Exception as e:
391
  logger.error(f"Erro ao obter usuário privilegiado: {e}")
392
  return None
393
-
394
  def pode_dar_ordens(self, numero: str) -> bool:
395
- """Verifica se usuário pode dar ordens"""
396
  user = self.get_usuario_privilegiado(numero)
397
  return user and user.get("pode_dar_ordens", False)
398
-
399
  def pode_usar_reset(self, numero: str) -> bool:
400
- """Verifica se usuário pode usar /reset"""
401
  user = self.get_usuario_privilegiado(numero)
402
  return user and user.get("pode_usar_reset", False)
403
-
404
  def pode_forcar_modo(self, numero: str) -> bool:
405
- """Verifica se usuário pode forçar modo"""
406
  user = self.get_usuario_privilegiado(numero)
407
  return user and user.get("pode_forcar_modo", False)
408
-
409
- def registrar_comando(self, numero: str, comando: str,
410
  parametros: str = None, sucesso: bool = True,
411
  resposta: str = None):
412
- """Registra comando executado"""
413
  try:
414
  self._execute_with_retry(
415
  """
416
- INSERT INTO comandos_executados
417
  (numero, comando, parametros, sucesso, resposta)
418
  VALUES (?, ?, ?, ?, ?)
419
  """,
@@ -421,12 +417,11 @@ class Database:
421
  commit=True,
422
  fetch=False
423
  )
424
-
425
- # Atualiza contador no usuário privilegiado
426
  if self.is_usuario_privilegiado(numero):
427
  self._execute_with_retry(
428
  """
429
- UPDATE usuarios_privilegiados
430
  SET ultimo_comando = ?,
431
  timestamp_comando = CURRENT_TIMESTAMP,
432
  comandos_executados = comandos_executados + 1,
@@ -437,32 +432,25 @@ class Database:
437
  commit=True,
438
  fetch=False
439
  )
440
-
441
  except Exception as e:
442
  logger.error(f"Erro ao registrar comando: {e}")
443
 
444
  # ================================================================
445
  # SISTEMA DE RESET (EXCLUSIVO PARA PRIVILEGIADOS)
446
  # ================================================================
447
-
448
  def resetar_contexto_usuario(self, numero: str, tipo: str = "completo") -> Dict:
449
- """
450
- Reseta contexto do usuário.
451
- Disponível APENAS para usuários com pode_usar_reset = True
452
- """
453
  try:
454
- # Verifica privilégio
455
  if not self.pode_usar_reset(numero):
456
  return {
457
  "sucesso": False,
458
  "erro": "Usuário não tem permissão para reset",
459
  "itens_apagados": 0
460
  }
461
-
462
- contexto_id = self._gerar_contexto_id(numero, 'auto')
463
  itens_apagados = 0
464
-
465
- # 1. Apaga mensagens
466
  result = self._execute_with_retry(
467
  "DELETE FROM mensagens WHERE numero = ?",
468
  (str(numero).strip(),),
@@ -470,8 +458,7 @@ class Database:
470
  fetch=False
471
  )
472
  itens_apagados += result if result else 0
473
-
474
- # 2. Apaga contexto
475
  self._execute_with_retry(
476
  "DELETE FROM contexto WHERE numero = ?",
477
  (str(numero).strip(),),
@@ -479,8 +466,7 @@ class Database:
479
  fetch=False
480
  )
481
  itens_apagados += 1
482
-
483
- # 3. Apaga transições de humor
484
  result = self._execute_with_retry(
485
  "DELETE FROM transicoes_humor WHERE numero = ?",
486
  (str(numero).strip(),),
@@ -488,8 +474,7 @@ class Database:
488
  fetch=False
489
  )
490
  itens_apagados += result if result else 0
491
-
492
- # 4. Apaga gírias aprendidas
493
  result = self._execute_with_retry(
494
  "DELETE FROM girias_aprendidas WHERE numero = ?",
495
  (str(numero).strip(),),
@@ -497,11 +482,10 @@ class Database:
497
  fetch=False
498
  )
499
  itens_apagados += result if result else 0
500
-
501
- # 5. Log do reset
502
  self._execute_with_retry(
503
  """
504
- INSERT INTO reset_log
505
  (numero, tipo_reset, itens_apagados, motivo, sucesso)
506
  VALUES (?, ?, ?, ?, 1)
507
  """,
@@ -509,22 +493,20 @@ class Database:
509
  commit=True,
510
  fetch=False
511
  )
512
-
513
  logger.info(f"✅ Reset completo para {numero}: {itens_apagados} itens apagados")
514
-
515
  return {
516
  "sucesso": True,
517
  "itens_apagados": itens_apagados,
518
  "mensagem": f"Contexto resetado com sucesso ({itens_apagados} itens removidos)"
519
  }
520
-
521
  except Exception as e:
522
  logger.error(f"Erro ao resetar contexto: {e}")
523
-
524
- # Log do erro
525
  self._execute_with_retry(
526
  """
527
- INSERT INTO reset_log
528
  (numero, tipo_reset, itens_apagados, motivo, sucesso)
529
  VALUES (?, ?, ?, ?, 0)
530
  """,
@@ -532,52 +514,43 @@ class Database:
532
  commit=True,
533
  fetch=False
534
  )
535
-
536
  return {
537
  "sucesso": False,
538
  "erro": str(e),
539
  "itens_apagados": 0
540
  }
541
-
542
  def resetar_tudo(self, confirmacao: bool = False) -> Dict:
543
- """
544
- Reseta TODA a base de dados (APENAS para emergências).
545
- Requer confirmação explícita.
546
- """
547
  if not confirmacao:
548
  return {
549
  "sucesso": False,
550
  "erro": "Confirmação necessária. Use confirmacao=True",
551
  "itens_apagados": 0
552
  }
553
-
554
  try:
555
- # Conta total antes
556
  total_mensagens = self._execute_with_retry(
557
  "SELECT COUNT(*) FROM mensagens",
558
  fetch=True
559
  )[0][0]
560
-
561
- # Apaga tudo (exceto usuários privilegiados)
562
  tabelas_para_resetar = [
563
  "mensagens", "contexto", "transicoes_humor",
564
- "girias_aprendidas", "training_examples",
565
  "comandos_executados", "reset_log"
566
  ]
567
-
568
- itens_totais = 0
569
  for tabela in tabelas_para_resetar:
570
  self._execute_with_retry(f"DELETE FROM {tabela}", commit=True, fetch=False)
571
- itens_totais += 1
572
-
573
  logger.warning(f"⚠️ RESET COMPLETO DA DATABASE: {total_mensagens} mensagens apagadas")
574
-
575
  return {
576
  "sucesso": True,
577
  "itens_apagados": total_mensagens,
578
  "mensagem": f"Database resetada completamente. {total_mensagens} mensagens removidas."
579
  }
580
-
581
  except Exception as e:
582
  logger.error(f"Erro no reset completo: {e}")
583
  return {
@@ -589,24 +562,22 @@ class Database:
589
  # ================================================================
590
  # MÉTODOS DE RECUPERAÇÃO (COM ISOLAMENTO)
591
  # ================================================================
592
-
593
  def recuperar_mensagens(self, numero: str, limite: int = 10) -> List[Tuple]:
594
- """Recupera histórico de mensagens"""
595
  try:
596
  results = self._execute_with_retry(
597
  """
598
  SELECT mensagem, resposta, is_reply, mensagem_original, reply_to_bot,
599
  humor, modo_resposta, emocao_detectada, confianca_emocao
600
- FROM mensagens
601
  WHERE numero = ? AND deletado = 0
602
- ORDER BY timestamp DESC
603
  LIMIT ?
604
  """,
605
  (str(numero).strip(), limite),
606
  fetch=True
607
  )
608
-
609
- # Retorna em ordem cronológica
610
  if results:
611
  return [
612
  (
@@ -616,13 +587,12 @@ class Database:
616
  for r in results[::-1]
617
  ]
618
  return []
619
-
620
  except Exception as e:
621
  logger.error(f"Erro ao recuperar mensagens: {e}")
622
  return []
623
-
624
  def recuperar_humor_atual(self, numero: str) -> str:
625
- """Recupera humor atual"""
626
  try:
627
  result = self._execute_with_retry(
628
  "SELECT humor_atual FROM contexto WHERE numero = ?",
@@ -632,9 +602,8 @@ class Database:
632
  return result[0][0] if result else "normal_ironico"
633
  except Exception:
634
  return "normal_ironico"
635
-
636
  def recuperar_modo_resposta(self, numero: str) -> str:
637
- """Recupera modo de resposta atual"""
638
  try:
639
  result = self._execute_with_retry(
640
  "SELECT modo_resposta FROM contexto WHERE numero = ?",
@@ -644,22 +613,21 @@ class Database:
644
  return result[0][0] if result else "normal_ironico"
645
  except Exception:
646
  return "normal_ironico"
647
-
648
  def recuperar_contexto_completo(self, numero: str) -> Dict:
649
- """Recupera contexto completo do usuário"""
650
  try:
651
  result = self._execute_with_retry(
652
  """
653
- SELECT contexto_id, tipo_contexto, historico, humor_atual,
654
  modo_resposta, nivel_transicao, humor_alvo, termos,
655
  girias, tom, emocao_tendencia, volatilidade
656
- FROM contexto
657
  WHERE numero = ?
658
  """,
659
  (str(numero).strip(),),
660
  fetch=True
661
  )
662
-
663
  if result:
664
  row = result[0]
665
  return {
@@ -676,8 +644,7 @@ class Database:
676
  "emocao_tendencia": row[10] or "neutral",
677
  "volatilidade": row[11] or 0.5
678
  }
679
-
680
- # Se não existir, cria contexto inicial
681
  return {
682
  "contexto_id": self._gerar_contexto_id(numero, 'auto'),
683
  "tipo_contexto": "pv",
@@ -692,7 +659,7 @@ class Database:
692
  "emocao_tendencia": "neutral",
693
  "volatilidade": 0.5
694
  }
695
-
696
  except Exception as e:
697
  logger.error(f"Erro ao recuperar contexto: {e}")
698
  return {
@@ -709,9 +676,8 @@ class Database:
709
  "emocao_tendencia": "neutral",
710
  "volatilidade": 0.5
711
  }
712
-
713
  def recuperar_historico_humor(self, numero: str, limite: int = 5) -> List[Dict]:
714
- """Recupera histórico de transições de humor"""
715
  try:
716
  results = self._execute_with_retry(
717
  """
@@ -725,7 +691,7 @@ class Database:
725
  (str(numero).strip(), limite),
726
  fetch=True
727
  )
728
-
729
  return [
730
  {
731
  "anterior": r[0],
@@ -742,14 +708,13 @@ class Database:
742
  except Exception as e:
743
  logger.error(f"Erro ao recuperar histórico humor: {e}")
744
  return []
745
-
746
  def recuperar_girias_usuario(self, numero: str) -> List[Dict]:
747
- """Recupera gírias aprendidas"""
748
  try:
749
  results = self._execute_with_retry(
750
  """
751
  SELECT giria, significado, contexto, frequencia, ultimo_uso
752
- FROM girias_aprendidas
753
  WHERE numero = ?
754
  ORDER BY frequencia DESC, ultimo_uso DESC
755
  LIMIT 20
@@ -757,7 +722,7 @@ class Database:
757
  (str(numero).strip(),),
758
  fetch=True
759
  )
760
-
761
  return [
762
  {
763
  'giria': r[0],
@@ -771,13 +736,12 @@ class Database:
771
  except Exception as e:
772
  logger.error(f"Erro ao recuperar gírias: {e}")
773
  return []
774
-
775
  def recuperar_training_examples(self, limite: int = 100) -> List[Dict]:
776
- """Recupera exemplos para treinamento"""
777
  try:
778
  results = self._execute_with_retry(
779
  """
780
- SELECT input_text, output_text, humor, modo_resposta,
781
  emocao_contexto, qualidade_score
782
  FROM training_examples
783
  WHERE usado = 0
@@ -787,7 +751,7 @@ class Database:
787
  (limite,),
788
  fetch=True
789
  )
790
-
791
  return [
792
  {
793
  "input": r[0],
@@ -802,9 +766,8 @@ class Database:
802
  except Exception as e:
803
  logger.error(f"Erro ao recuperar exemplos: {e}")
804
  return []
805
-
806
  def marcar_examples_como_usados(self):
807
- """Marca exemplos como usados"""
808
  try:
809
  self._execute_with_retry(
810
  "UPDATE training_examples SET usado = 1 WHERE usado = 0",
@@ -818,24 +781,23 @@ class Database:
818
  # ================================================================
819
  # MÉTODOS DE SALVAMENTO (COM CONTEXTO)
820
  # ================================================================
821
-
822
  def salvar_mensagem(self, usuario: str, mensagem: str, resposta: str,
823
  numero: str = '', is_reply: bool = False,
824
  mensagem_original: str = None, reply_to_bot: bool = False,
825
- humor: str = 'normal_ironico',
826
  modo_resposta: str = 'normal_ironico',
827
  emocao_detectada: str = None,
828
  confianca_emocao: float = 0.5):
829
- """Salva mensagem com contexto completo"""
830
  try:
831
  numero_final = str(numero or usuario).strip()
832
  contexto_id = self._gerar_contexto_id(numero_final, 'auto')
833
  tipo_contexto = "grupo" if ("@g.us" in numero_final or "120363" in numero_final) else "pv"
834
-
835
  self._execute_with_retry(
836
  """
837
- INSERT INTO mensagens
838
- (usuario, mensagem, resposta, numero, contexto_id, tipo_contexto,
839
  is_reply, mensagem_original, reply_to_bot, humor, modo_resposta,
840
  emocao_detectada, confianca_emocao)
841
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
@@ -849,74 +811,69 @@ class Database:
849
  commit=True,
850
  fetch=False
851
  )
852
-
853
  logger.debug(f"✅ Mensagem salva: {numero_final} ({tipo_contexto})")
854
  return True
855
-
856
  except Exception as e:
857
  logger.error(f"Erro ao salvar mensagem: {e}")
858
  return False
859
-
860
  def salvar_contexto(self, numero: str, **kwargs):
861
- """Salva/atualiza contexto do usuário"""
862
  try:
863
  numero_final = str(numero).strip()
864
  contexto_id = self._gerar_contexto_id(numero_final, 'auto')
865
  tipo_contexto = "grupo" if ("@g.us" in numero_final or "120363" in numero_final) else "pv"
866
-
867
- # Prepara campos
868
  campos = {
869
  "numero": numero_final,
870
  "contexto_id": contexto_id,
871
  "tipo_contexto": tipo_contexto,
872
  "data_atualizacao": "CURRENT_TIMESTAMP"
873
  }
874
-
875
- # Adiciona campos fornecidos
876
  campos.update(kwargs)
877
-
878
- # Constrói query dinâmica
879
  colunas = []
880
  valores = []
881
  placeholders = []
882
-
883
  for key, value in campos.items():
884
  if key != "data_atualizacao":
885
  colunas.append(key)
886
  valores.append(value)
887
  placeholders.append("?")
888
-
889
  colunas.append("data_atualizacao")
890
  placeholders.append("CURRENT_TIMESTAMP")
891
-
892
  query = f"""
893
- INSERT OR REPLACE INTO contexto
894
  ({', '.join(colunas)})
895
  VALUES ({', '.join(placeholders)})
896
  """
897
-
898
  self._execute_with_retry(
899
  query,
900
  tuple(valores),
901
  commit=True,
902
  fetch=False
903
  )
904
-
905
  return True
906
-
907
  except Exception as e:
908
  logger.error(f"Erro ao salvar contexto: {e}")
909
  return False
910
-
911
  def salvar_transicao_humor(self, numero: str, humor_anterior: str,
912
  humor_novo: str, emocao_trigger: str = None,
913
  confianca_emocao: float = 0.5,
914
  nivel_transicao: int = 0,
915
  razao: str = "", intensidade: float = 0.5):
916
- """Registra transição de humor"""
917
  try:
918
  contexto_id = self._gerar_contexto_id(numero, 'auto')
919
-
920
  self._execute_with_retry(
921
  """
922
  INSERT INTO transicoes_humor
@@ -932,23 +889,94 @@ class Database:
932
  commit=True,
933
  fetch=False
934
  )
935
-
936
- # Atualiza humor no contexto
937
  self.salvar_contexto(
938
  numero=numero,
939
  humor_atual=humor_novo,
940
  nivel_transicao=nivel_transicao,
941
  humor_alvo=humor_novo if nivel_transicao >= 3 else humor_anterior
942
  )
943
-
944
  logger.debug(f"🎭 Transição salva: {humor_anterior} → {humor_novo} (nivel: {nivel_transicao})")
945
-
946
  except Exception as e:
947
  logger.error(f"Erro ao salvar transição: {e}")
948
-
949
  # ================================================================
950
- # MÉTODOS FALTANTES (ADICIONADOS AGORA)
951
- # =========================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
952
  # ================================================================
953
  # FECHAMENTO SEGURO DA CONEXÃO
954
  # ================================================================
@@ -960,7 +988,8 @@ class Database:
960
  except:
961
  pass
962
 
963
- # Garante fechamento ao encerrar o app
 
964
  import atexit
965
  db_instance = Database(config.DB_PATH)
966
  atexit.register(db_instance.close)
 
7
  ✅ Transições graduais de humor (3 níveis)
8
  ✅ Memória emocional BERT GoEmotions
9
  ✅ Backup e otimização automática
10
+ ✅ MÉTODOS FALTANTES ADICIONADOS: salvar_training_example, registrar_tom_usuario
11
  """
12
+
13
  import sqlite3
14
  import time
15
  import os
 
27
  self.db_path = db_path or config.DB_PATH
28
  self.max_retries = 5
29
  self.retry_delay = 0.1
30
+
31
  # Cria diretório se necessário
32
  db_dir = os.path.dirname(self.db_path)
33
  if db_dir:
34
  os.makedirs(db_dir, exist_ok=True)
35
+
36
  self._init_db()
37
  self._ensure_all_columns_and_indexes()
38
  self._sincronizar_usuarios_privilegiados() # Sincroniza com config
39
+
40
  logger.info(f"✅ Banco V21 inicializado: {self.db_path}")
41
 
42
  # ================================================================
43
  # ISOLAMENTO CRIPTOGRÁFICO (V21 MELHORADO)
44
  # ================================================================
45
+
46
  def _gerar_contexto_id(self, numero: str, tipo: str = 'auto') -> str:
47
  """
48
  Gera ID único isolado com salt dinâmico.
 
53
  tipo = "grupo"
54
  else:
55
  tipo = "pv"
56
+
57
  # Salt dinâmico baseado em data
58
  data_semana = datetime.now().strftime("%Y-%W")
59
  salt = f"AKIRA_V21_{data_semana}_ISOLATION"
60
+
61
  # Hash único
62
  raw = f"{str(numero).strip()}|{tipo}|{salt}"
63
  return hashlib.sha256(raw.encode()).hexdigest()[:32]
 
65
  # ================================================================
66
  # CONEXÃO COM OTIMIZAÇÕES
67
  # ================================================================
68
+
69
  def _get_connection(self) -> sqlite3.Connection:
70
  for attempt in range(self.max_retries):
71
  try:
 
86
  raise
87
  raise sqlite3.OperationalError("Falha após retries")
88
 
89
+ def _execute_with_retry(self, query: str, params: Optional[tuple] = None,
90
  commit: bool = False, fetch: bool = True):
91
  for attempt in range(self.max_retries):
92
  try:
 
96
  c.execute(query, params)
97
  else:
98
  c.execute(query)
99
+
100
  if commit:
101
  conn.commit()
102
+
103
  if fetch and query.strip().upper().startswith('SELECT'):
104
  return c.fetchall()
105
  elif fetch:
 
119
  # ================================================================
120
  # VERIFICAÇÃO E CRIAÇÃO DE COLUNAS/ÍNDICES
121
  # ================================================================
122
+
123
  def _ensure_all_columns_and_indexes(self):
124
  """Garante que todas as colunas e índices existam"""
125
  try:
126
  with self._get_connection() as conn:
127
  c = conn.cursor()
128
+
129
  # Índices para performance
130
  indexes = [
131
  "CREATE INDEX IF NOT EXISTS idx_mensagens_numero ON mensagens(numero, deletado)",
 
139
  "CREATE INDEX IF NOT EXISTS idx_comandos_numero ON comandos_executados(numero, timestamp)",
140
  "CREATE INDEX IF NOT EXISTS idx_reset_numero ON reset_log(numero, timestamp)"
141
  ]
142
+
143
  for idx_query in indexes:
144
  try:
145
  c.execute(idx_query)
146
  except Exception as e:
147
  logger.warning(f"Erro ao criar índice: {e}")
148
+
149
  conn.commit()
150
  logger.info("✅ Índices verificados/criados")
151
+
152
  except Exception as e:
153
  logger.error(f"Erro ao verificar colunas/índices: {e}")
154
 
155
  # ================================================================
156
  # INICIALIZAÇÃO COMPLETA
157
  # ================================================================
158
+
159
  def _init_db(self):
160
  try:
161
  with self._get_connection() as conn:
162
  c = conn.cursor()
163
+
164
  # TABELA PRINCIPAL DE MENSAGENS
165
  c.execute('''
166
  CREATE TABLE IF NOT EXISTS mensagens (
 
182
  deletado BOOLEAN DEFAULT 0
183
  )
184
  ''')
185
+
186
  # TABELA DE USUÁRIOS PRIVILEGIADOS (V21)
187
  c.execute('''
188
  CREATE TABLE IF NOT EXISTS usuarios_privilegiados (
 
202
  data_atualizacao DATETIME DEFAULT CURRENT_TIMESTAMP
203
  )
204
  ''')
205
+
206
  # TABELA DE CONTEXTO
207
  c.execute('''
208
  CREATE TABLE IF NOT EXISTS contexto (
 
224
  data_atualizacao DATETIME DEFAULT CURRENT_TIMESTAMP
225
  )
226
  ''')
227
+
228
  # TABELA DE TRANSIÇÕES DE HUMOR
229
  c.execute('''
230
  CREATE TABLE IF NOT EXISTS transicoes_humor (
 
241
  timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
242
  )
243
  ''')
244
+
245
  # TABELA DE EXEMPLOS DE TREINAMENTO
246
  c.execute('''
247
  CREATE TABLE IF NOT EXISTS training_examples (
 
256
  timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
257
  )
258
  ''')
259
+
260
  # TABELA DE GÍRIAS APRENDIDAS
261
  c.execute('''
262
  CREATE TABLE IF NOT EXISTS girias_aprendidas (
 
271
  data_criacao DATETIME DEFAULT CURRENT_TIMESTAMP
272
  )
273
  ''')
274
+
275
  # TABELA DE COMANDOS EXECUTADOS
276
  c.execute('''
277
  CREATE TABLE IF NOT EXISTS comandos_executados (
 
284
  timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
285
  )
286
  ''')
287
+
288
  # TABELA DE RESETS (LOG)
289
  c.execute('''
290
  CREATE TABLE IF NOT EXISTS reset_log (
 
297
  timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
298
  )
299
  ''')
300
+
301
  conn.commit()
302
+
303
  logger.info("✅ Todas as tabelas criadas/verificadas")
304
+
305
  except Exception as e:
306
  logger.error(f"❌ Erro ao criar tabelas: {e}")
307
  raise
 
309
  # ================================================================
310
  # SINCRONIZAÇÃO DE USUÁRIOS PRIVILEGIADOS
311
  # ================================================================
312
+
313
  def _sincronizar_usuarios_privilegiados(self):
314
  """Sincroniza usuários privilegiados do config.py com o banco"""
315
  try:
316
  for numero, dados in config.USUARIOS_PRIVILEGIADOS.items():
317
  self._execute_with_retry(
318
  """
319
+ INSERT OR REPLACE INTO usuarios_privilegiados
320
+ (numero, nome, nome_curto, tom_inicial, pode_dar_ordens,
321
  pode_usar_reset, pode_forcar_modo, nivel_acesso, data_atualizacao)
322
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
323
  """,
 
334
  commit=True,
335
  fetch=False
336
  )
337
+
338
  logger.info(f"✅ Usuários privilegiados sincronizados: {len(config.USUARIOS_PRIVILEGIADOS)}")
339
+
340
  except Exception as e:
341
  logger.error(f"Erro ao sincronizar usuários privilegiados: {e}")
342
 
343
  # ================================================================
344
  # VERIFICAÇÃO DE PRIVILÉGIOS (ROBUSTA)
345
  # ================================================================
346
+
347
  def is_usuario_privilegiado(self, numero: str) -> bool:
 
348
  try:
349
  result = self._execute_with_retry(
350
  "SELECT 1 FROM usuarios_privilegiados WHERE numero = ?",
 
355
  except Exception as e:
356
  logger.error(f"Erro ao verificar privilégio: {e}")
357
  return False
358
+
359
  def get_usuario_privilegiado(self, numero: str) -> Optional[Dict]:
 
360
  try:
361
  result = self._execute_with_retry(
362
  """
363
  SELECT numero, nome, nome_curto, tom_inicial, pode_dar_ordens,
364
  pode_usar_reset, pode_forcar_modo, nivel_acesso,
365
  ultimo_comando, timestamp_comando, comandos_executados
366
+ FROM usuarios_privilegiados
367
  WHERE numero = ?
368
  """,
369
  (str(numero).strip(),),
370
  fetch=True
371
  )
372
+
373
  if result:
374
  row = result[0]
375
  return {
 
386
  "comandos_executados": row[10]
387
  }
388
  return None
389
+
390
  except Exception as e:
391
  logger.error(f"Erro ao obter usuário privilegiado: {e}")
392
  return None
393
+
394
  def pode_dar_ordens(self, numero: str) -> bool:
 
395
  user = self.get_usuario_privilegiado(numero)
396
  return user and user.get("pode_dar_ordens", False)
397
+
398
  def pode_usar_reset(self, numero: str) -> bool:
 
399
  user = self.get_usuario_privilegiado(numero)
400
  return user and user.get("pode_usar_reset", False)
401
+
402
  def pode_forcar_modo(self, numero: str) -> bool:
 
403
  user = self.get_usuario_privilegiado(numero)
404
  return user and user.get("pode_forcar_modo", False)
405
+
406
+ def registrar_comando(self, numero: str, comando: str,
407
  parametros: str = None, sucesso: bool = True,
408
  resposta: str = None):
 
409
  try:
410
  self._execute_with_retry(
411
  """
412
+ INSERT INTO comandos_executados
413
  (numero, comando, parametros, sucesso, resposta)
414
  VALUES (?, ?, ?, ?, ?)
415
  """,
 
417
  commit=True,
418
  fetch=False
419
  )
420
+
 
421
  if self.is_usuario_privilegiado(numero):
422
  self._execute_with_retry(
423
  """
424
+ UPDATE usuarios_privilegiados
425
  SET ultimo_comando = ?,
426
  timestamp_comando = CURRENT_TIMESTAMP,
427
  comandos_executados = comandos_executados + 1,
 
432
  commit=True,
433
  fetch=False
434
  )
435
+
436
  except Exception as e:
437
  logger.error(f"Erro ao registrar comando: {e}")
438
 
439
  # ================================================================
440
  # SISTEMA DE RESET (EXCLUSIVO PARA PRIVILEGIADOS)
441
  # ================================================================
442
+
443
  def resetar_contexto_usuario(self, numero: str, tipo: str = "completo") -> Dict:
 
 
 
 
444
  try:
 
445
  if not self.pode_usar_reset(numero):
446
  return {
447
  "sucesso": False,
448
  "erro": "Usuário não tem permissão para reset",
449
  "itens_apagados": 0
450
  }
451
+
 
452
  itens_apagados = 0
453
+
 
454
  result = self._execute_with_retry(
455
  "DELETE FROM mensagens WHERE numero = ?",
456
  (str(numero).strip(),),
 
458
  fetch=False
459
  )
460
  itens_apagados += result if result else 0
461
+
 
462
  self._execute_with_retry(
463
  "DELETE FROM contexto WHERE numero = ?",
464
  (str(numero).strip(),),
 
466
  fetch=False
467
  )
468
  itens_apagados += 1
469
+
 
470
  result = self._execute_with_retry(
471
  "DELETE FROM transicoes_humor WHERE numero = ?",
472
  (str(numero).strip(),),
 
474
  fetch=False
475
  )
476
  itens_apagados += result if result else 0
477
+
 
478
  result = self._execute_with_retry(
479
  "DELETE FROM girias_aprendidas WHERE numero = ?",
480
  (str(numero).strip(),),
 
482
  fetch=False
483
  )
484
  itens_apagados += result if result else 0
485
+
 
486
  self._execute_with_retry(
487
  """
488
+ INSERT INTO reset_log
489
  (numero, tipo_reset, itens_apagados, motivo, sucesso)
490
  VALUES (?, ?, ?, ?, 1)
491
  """,
 
493
  commit=True,
494
  fetch=False
495
  )
496
+
497
  logger.info(f"✅ Reset completo para {numero}: {itens_apagados} itens apagados")
498
+
499
  return {
500
  "sucesso": True,
501
  "itens_apagados": itens_apagados,
502
  "mensagem": f"Contexto resetado com sucesso ({itens_apagados} itens removidos)"
503
  }
504
+
505
  except Exception as e:
506
  logger.error(f"Erro ao resetar contexto: {e}")
 
 
507
  self._execute_with_retry(
508
  """
509
+ INSERT INTO reset_log
510
  (numero, tipo_reset, itens_apagados, motivo, sucesso)
511
  VALUES (?, ?, ?, ?, 0)
512
  """,
 
514
  commit=True,
515
  fetch=False
516
  )
 
517
  return {
518
  "sucesso": False,
519
  "erro": str(e),
520
  "itens_apagados": 0
521
  }
522
+
523
  def resetar_tudo(self, confirmacao: bool = False) -> Dict:
 
 
 
 
524
  if not confirmacao:
525
  return {
526
  "sucesso": False,
527
  "erro": "Confirmação necessária. Use confirmacao=True",
528
  "itens_apagados": 0
529
  }
530
+
531
  try:
 
532
  total_mensagens = self._execute_with_retry(
533
  "SELECT COUNT(*) FROM mensagens",
534
  fetch=True
535
  )[0][0]
536
+
 
537
  tabelas_para_resetar = [
538
  "mensagens", "contexto", "transicoes_humor",
539
+ "girias_aprendidas", "training_examples",
540
  "comandos_executados", "reset_log"
541
  ]
542
+
 
543
  for tabela in tabelas_para_resetar:
544
  self._execute_with_retry(f"DELETE FROM {tabela}", commit=True, fetch=False)
545
+
 
546
  logger.warning(f"⚠️ RESET COMPLETO DA DATABASE: {total_mensagens} mensagens apagadas")
547
+
548
  return {
549
  "sucesso": True,
550
  "itens_apagados": total_mensagens,
551
  "mensagem": f"Database resetada completamente. {total_mensagens} mensagens removidas."
552
  }
553
+
554
  except Exception as e:
555
  logger.error(f"Erro no reset completo: {e}")
556
  return {
 
562
  # ================================================================
563
  # MÉTODOS DE RECUPERAÇÃO (COM ISOLAMENTO)
564
  # ================================================================
565
+
566
  def recuperar_mensagens(self, numero: str, limite: int = 10) -> List[Tuple]:
 
567
  try:
568
  results = self._execute_with_retry(
569
  """
570
  SELECT mensagem, resposta, is_reply, mensagem_original, reply_to_bot,
571
  humor, modo_resposta, emocao_detectada, confianca_emocao
572
+ FROM mensagens
573
  WHERE numero = ? AND deletado = 0
574
+ ORDER BY timestamp DESC
575
  LIMIT ?
576
  """,
577
  (str(numero).strip(), limite),
578
  fetch=True
579
  )
580
+
 
581
  if results:
582
  return [
583
  (
 
587
  for r in results[::-1]
588
  ]
589
  return []
590
+
591
  except Exception as e:
592
  logger.error(f"Erro ao recuperar mensagens: {e}")
593
  return []
594
+
595
  def recuperar_humor_atual(self, numero: str) -> str:
 
596
  try:
597
  result = self._execute_with_retry(
598
  "SELECT humor_atual FROM contexto WHERE numero = ?",
 
602
  return result[0][0] if result else "normal_ironico"
603
  except Exception:
604
  return "normal_ironico"
605
+
606
  def recuperar_modo_resposta(self, numero: str) -> str:
 
607
  try:
608
  result = self._execute_with_retry(
609
  "SELECT modo_resposta FROM contexto WHERE numero = ?",
 
613
  return result[0][0] if result else "normal_ironico"
614
  except Exception:
615
  return "normal_ironico"
616
+
617
  def recuperar_contexto_completo(self, numero: str) -> Dict:
 
618
  try:
619
  result = self._execute_with_retry(
620
  """
621
+ SELECT contexto_id, tipo_contexto, historico, humor_atual,
622
  modo_resposta, nivel_transicao, humor_alvo, termos,
623
  girias, tom, emocao_tendencia, volatilidade
624
+ FROM contexto
625
  WHERE numero = ?
626
  """,
627
  (str(numero).strip(),),
628
  fetch=True
629
  )
630
+
631
  if result:
632
  row = result[0]
633
  return {
 
644
  "emocao_tendencia": row[10] or "neutral",
645
  "volatilidade": row[11] or 0.5
646
  }
647
+
 
648
  return {
649
  "contexto_id": self._gerar_contexto_id(numero, 'auto'),
650
  "tipo_contexto": "pv",
 
659
  "emocao_tendencia": "neutral",
660
  "volatilidade": 0.5
661
  }
662
+
663
  except Exception as e:
664
  logger.error(f"Erro ao recuperar contexto: {e}")
665
  return {
 
676
  "emocao_tendencia": "neutral",
677
  "volatilidade": 0.5
678
  }
679
+
680
  def recuperar_historico_humor(self, numero: str, limite: int = 5) -> List[Dict]:
 
681
  try:
682
  results = self._execute_with_retry(
683
  """
 
691
  (str(numero).strip(), limite),
692
  fetch=True
693
  )
694
+
695
  return [
696
  {
697
  "anterior": r[0],
 
708
  except Exception as e:
709
  logger.error(f"Erro ao recuperar histórico humor: {e}")
710
  return []
711
+
712
  def recuperar_girias_usuario(self, numero: str) -> List[Dict]:
 
713
  try:
714
  results = self._execute_with_retry(
715
  """
716
  SELECT giria, significado, contexto, frequencia, ultimo_uso
717
+ FROM girias_aprendidas
718
  WHERE numero = ?
719
  ORDER BY frequencia DESC, ultimo_uso DESC
720
  LIMIT 20
 
722
  (str(numero).strip(),),
723
  fetch=True
724
  )
725
+
726
  return [
727
  {
728
  'giria': r[0],
 
736
  except Exception as e:
737
  logger.error(f"Erro ao recuperar gírias: {e}")
738
  return []
739
+
740
  def recuperar_training_examples(self, limite: int = 100) -> List[Dict]:
 
741
  try:
742
  results = self._execute_with_retry(
743
  """
744
+ SELECT input_text, output_text, humor, modo_resposta,
745
  emocao_contexto, qualidade_score
746
  FROM training_examples
747
  WHERE usado = 0
 
751
  (limite,),
752
  fetch=True
753
  )
754
+
755
  return [
756
  {
757
  "input": r[0],
 
766
  except Exception as e:
767
  logger.error(f"Erro ao recuperar exemplos: {e}")
768
  return []
769
+
770
  def marcar_examples_como_usados(self):
 
771
  try:
772
  self._execute_with_retry(
773
  "UPDATE training_examples SET usado = 1 WHERE usado = 0",
 
781
  # ================================================================
782
  # MÉTODOS DE SALVAMENTO (COM CONTEXTO)
783
  # ================================================================
784
+
785
  def salvar_mensagem(self, usuario: str, mensagem: str, resposta: str,
786
  numero: str = '', is_reply: bool = False,
787
  mensagem_original: str = None, reply_to_bot: bool = False,
788
+ humor: str = 'normal_ironico',
789
  modo_resposta: str = 'normal_ironico',
790
  emocao_detectada: str = None,
791
  confianca_emocao: float = 0.5):
 
792
  try:
793
  numero_final = str(numero or usuario).strip()
794
  contexto_id = self._gerar_contexto_id(numero_final, 'auto')
795
  tipo_contexto = "grupo" if ("@g.us" in numero_final or "120363" in numero_final) else "pv"
796
+
797
  self._execute_with_retry(
798
  """
799
+ INSERT INTO mensagens
800
+ (usuario, mensagem, resposta, numero, contexto_id, tipo_contexto,
801
  is_reply, mensagem_original, reply_to_bot, humor, modo_resposta,
802
  emocao_detectada, confianca_emocao)
803
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
 
811
  commit=True,
812
  fetch=False
813
  )
814
+
815
  logger.debug(f"✅ Mensagem salva: {numero_final} ({tipo_contexto})")
816
  return True
817
+
818
  except Exception as e:
819
  logger.error(f"Erro ao salvar mensagem: {e}")
820
  return False
821
+
822
  def salvar_contexto(self, numero: str, **kwargs):
 
823
  try:
824
  numero_final = str(numero).strip()
825
  contexto_id = self._gerar_contexto_id(numero_final, 'auto')
826
  tipo_contexto = "grupo" if ("@g.us" in numero_final or "120363" in numero_final) else "pv"
827
+
 
828
  campos = {
829
  "numero": numero_final,
830
  "contexto_id": contexto_id,
831
  "tipo_contexto": tipo_contexto,
832
  "data_atualizacao": "CURRENT_TIMESTAMP"
833
  }
834
+
 
835
  campos.update(kwargs)
836
+
 
837
  colunas = []
838
  valores = []
839
  placeholders = []
840
+
841
  for key, value in campos.items():
842
  if key != "data_atualizacao":
843
  colunas.append(key)
844
  valores.append(value)
845
  placeholders.append("?")
846
+
847
  colunas.append("data_atualizacao")
848
  placeholders.append("CURRENT_TIMESTAMP")
849
+
850
  query = f"""
851
+ INSERT OR REPLACE INTO contexto
852
  ({', '.join(colunas)})
853
  VALUES ({', '.join(placeholders)})
854
  """
855
+
856
  self._execute_with_retry(
857
  query,
858
  tuple(valores),
859
  commit=True,
860
  fetch=False
861
  )
862
+
863
  return True
864
+
865
  except Exception as e:
866
  logger.error(f"Erro ao salvar contexto: {e}")
867
  return False
868
+
869
  def salvar_transicao_humor(self, numero: str, humor_anterior: str,
870
  humor_novo: str, emocao_trigger: str = None,
871
  confianca_emocao: float = 0.5,
872
  nivel_transicao: int = 0,
873
  razao: str = "", intensidade: float = 0.5):
 
874
  try:
875
  contexto_id = self._gerar_contexto_id(numero, 'auto')
876
+
877
  self._execute_with_retry(
878
  """
879
  INSERT INTO transicoes_humor
 
889
  commit=True,
890
  fetch=False
891
  )
892
+
 
893
  self.salvar_contexto(
894
  numero=numero,
895
  humor_atual=humor_novo,
896
  nivel_transicao=nivel_transicao,
897
  humor_alvo=humor_novo if nivel_transicao >= 3 else humor_anterior
898
  )
899
+
900
  logger.debug(f"🎭 Transição salva: {humor_anterior} → {humor_novo} (nivel: {nivel_transicao})")
901
+
902
  except Exception as e:
903
  logger.error(f"Erro ao salvar transição: {e}")
904
+
905
  # ================================================================
906
+ # MÉTODOS FALTANTES ADICIONADOS (RESOLVEM OS ERROS)
907
+ # ================================================================
908
+
909
+ def salvar_training_example(self, input_text: str, output_text: str,
910
+ humor: str = "normal_ironico",
911
+ modo_resposta: str = "normal_ironico",
912
+ emocao_contexto: str = None,
913
+ qualidade_score: float = 1.0) -> bool:
914
+ """
915
+ Salva um exemplo de alta qualidade para treinamento futuro.
916
+ """
917
+ try:
918
+ self._execute_with_retry(
919
+ """
920
+ INSERT INTO training_examples
921
+ (input_text, output_text, humor, modo_resposta, emocao_contexto, qualidade_score)
922
+ VALUES (?, ?, ?, ?, ?, ?)
923
+ """,
924
+ (input_text[:2000], output_text[:2000], humor, modo_resposta, emocao_contexto, qualidade_score),
925
+ commit=True,
926
+ fetch=False
927
+ )
928
+ logger.info(f"✅ Training example salvo | Score: {qualidade_score:.2f}")
929
+ return True
930
+ except Exception as e:
931
+ logger.error(f"❌ Erro ao salvar training example: {e}")
932
+ return False
933
+
934
+ def registrar_tom_usuario(self, numero: str, tom: str, confianca: float = 0.6,
935
+ mensagem_contexto: str = None) -> bool:
936
+ """
937
+ Registra o tom emocional detectado do usuário.
938
+ Atualiza também a tendência no contexto.
939
+ """
940
+ try:
941
+ numero_final = str(numero).strip()
942
+ contexto_id = self._gerar_contexto_id(numero_final, 'auto')
943
+ tipo_contexto = "grupo" if ("@g.us" in numero_final or "120363" in numero_final) else "pv"
944
+
945
+ self._execute_with_retry(
946
+ """
947
+ INSERT INTO mensagens
948
+ (usuario, mensagem, resposta, numero, contexto_id, tipo_contexto,
949
+ humor, modo_resposta, emocao_detectada, confianca_emocao)
950
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
951
+ """,
952
+ (
953
+ "SISTEMA_TOM",
954
+ f"[TOM_DETECTADO: {tom}] {mensagem_contexto or ''}",
955
+ "",
956
+ numero_final,
957
+ contexto_id,
958
+ tipo_contexto,
959
+ "normal_ironico",
960
+ "normal_ironico",
961
+ tom,
962
+ confianca
963
+ ),
964
+ commit=True,
965
+ fetch=False
966
+ )
967
+
968
+ self.salvar_contexto(
969
+ numero=numero_final,
970
+ tom=tom,
971
+ emocao_tendencia=tom
972
+ )
973
+
974
+ logger.info(f"✅ Tom registrado: {tom} (confiança: {confianca:.2f}) para {numero_final}")
975
+ return True
976
+ except Exception as e:
977
+ logger.error(f"❌ Erro ao registrar tom do usuário: {e}")
978
+ return False
979
+
980
  # ================================================================
981
  # FECHAMENTO SEGURO DA CONEXÃO
982
  # ================================================================
 
988
  except:
989
  pass
990
 
991
+
992
+ # Instância global e fechamento seguro ao encerrar o app
993
  import atexit
994
  db_instance = Database(config.DB_PATH)
995
  atexit.register(db_instance.close)