akra35567 commited on
Commit
dbeac69
·
verified ·
1 Parent(s): f3b7acc

Create database.pt

Browse files
Files changed (1) hide show
  1. database.pt +294 -0
database.pt ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # database.py — V23 — TODAS AS TABELAS DO V18 + NOVAS (POO)
2
+ import sqlite3
3
+ import time
4
+ import os
5
+ import json
6
+ from typing import List, Dict, Any, Tuple
7
+ from loguru import logger
8
+
9
+ class Database:
10
+ def __init__(self, db_path: str = "/app/akira.db"):
11
+ self.db_path = db_path
12
+ self.max_retries = 5
13
+ self.retry_delay = 0.1
14
+ os.makedirs(os.path.dirname(db_path), exist_ok=True)
15
+ self._init_db()
16
+ self._ensure_all_columns_and_indexes()
17
+
18
+ def _get_connection(self) -> sqlite3.Connection:
19
+ for attempt in range(self.max_retries):
20
+ try:
21
+ conn = sqlite3.connect(self.db_path, timeout=30.0, check_same_thread=False)
22
+ conn.execute('PRAGMA journal_mode=WAL')
23
+ conn.execute('PRAGMA synchronous=NORMAL')
24
+ conn.execute('PRAGMA busy_timeout=30000')
25
+ conn.execute('PRAGMA foreign_keys=ON')
26
+ return conn
27
+ except sqlite3.OperationalError as e:
28
+ if "database is locked" in str(e) and attempt < self.max_retries - 1:
29
+ time.sleep(self.retry_delay * (2 ** attempt))
30
+ continue
31
+ logger.error(f"Falha ao conectar: {e}")
32
+ raise
33
+
34
+ def _execute(self, query: str, params: tuple = (), commit: bool = False):
35
+ for attempt in range(self.max_retries):
36
+ try:
37
+ with self._get_connection() as conn:
38
+ c = conn.cursor()
39
+ c.execute(query, params)
40
+ result = c.fetchall() if query.strip().upper().startswith('SELECT') else None
41
+ if commit:
42
+ conn.commit()
43
+ return result
44
+ except sqlite3.OperationalError as e:
45
+ if "database is locked" in str(e) and attempt < self.max_retries - 1:
46
+ time.sleep(self.retry_delay * (2 ** attempt))
47
+ continue
48
+ logger.error(f"Erro SQL: {e}")
49
+ raise
50
+
51
+ # ================================================================
52
+ # INICIALIZAÇÃO — TODAS AS TABELAS DO V18 + NOVAS
53
+ # ================================================================
54
+ def _init_db(self):
55
+ with self._get_connection() as conn:
56
+ c = conn.cursor()
57
+ c.executescript('''
58
+ -- TABELAS ORIGINAIS (V18)
59
+ CREATE TABLE IF NOT EXISTS aprendizado (
60
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
61
+ usuario TEXT,
62
+ dado TEXT,
63
+ valor TEXT
64
+ );
65
+ CREATE TABLE IF NOT EXISTS exemplos (
66
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
67
+ tipo TEXT NOT NULL,
68
+ entrada TEXT NOT NULL,
69
+ resposta TEXT NOT NULL
70
+ );
71
+ CREATE TABLE IF NOT EXISTS info_geral (
72
+ chave TEXT PRIMARY KEY,
73
+ valor TEXT NOT NULL
74
+ );
75
+ CREATE TABLE IF NOT EXISTS estilos (
76
+ numero_usuario TEXT PRIMARY KEY,
77
+ estilo TEXT NOT NULL
78
+ );
79
+ CREATE TABLE IF NOT EXISTS preferencias_tom (
80
+ numero_usuario TEXT PRIMARY KEY,
81
+ tom TEXT NOT NULL
82
+ );
83
+ CREATE TABLE IF NOT EXISTS afinidades (
84
+ numero_usuario TEXT PRIMARY KEY,
85
+ afinidade REAL NOT NULL
86
+ );
87
+ CREATE TABLE IF NOT EXISTS termos (
88
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
89
+ numero_usuario TEXT NOT NULL,
90
+ termo TEXT NOT NULL,
91
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
92
+ );
93
+ CREATE TABLE IF NOT EXISTS aprendizados (
94
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
95
+ numero_usuario TEXT NOT NULL,
96
+ chave TEXT NOT NULL,
97
+ valor TEXT NOT NULL,
98
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
99
+ );
100
+ CREATE TABLE IF NOT EXISTS aprendizado_detalhado (
101
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
102
+ numero_usuario TEXT NOT NULL,
103
+ tipo TEXT NOT NULL,
104
+ chave TEXT NOT NULL,
105
+ valor TEXT NOT NULL,
106
+ fonte TEXT,
107
+ confianca REAL DEFAULT 1.0,
108
+ contexto TEXT,
109
+ criado_em DATETIME DEFAULT CURRENT_TIMESTAMP,
110
+ atualizado_em DATETIME DEFAULT CURRENT_TIMESTAMP
111
+ );
112
+ CREATE TABLE IF NOT EXISTS vocabulario_patenteado (
113
+ termo TEXT PRIMARY KEY,
114
+ definicao TEXT NOT NULL,
115
+ uso TEXT NOT NULL,
116
+ exemplo TEXT NOT NULL
117
+ );
118
+ CREATE TABLE IF NOT EXISTS usuarios_privilegiados (
119
+ numero_usuario TEXT PRIMARY KEY,
120
+ nome TEXT NOT NULL
121
+ );
122
+ 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
+ CREATE TABLE IF NOT EXISTS embeddings (
129
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
130
+ numero_usuario TEXT,
131
+ source_type TEXT,
132
+ texto TEXT,
133
+ embedding BLOB NOT NULL
134
+ );
135
+ CREATE TABLE IF NOT EXISTS mensagens (
136
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
137
+ usuario TEXT NOT NULL,
138
+ mensagem TEXT NOT NULL,
139
+ resposta TEXT NOT NULL,
140
+ numero TEXT,
141
+ is_reply BOOLEAN DEFAULT 0,
142
+ mensagem_original TEXT,
143
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
144
+ );
145
+ CREATE TABLE IF NOT EXISTS emocao_exemplos (
146
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
147
+ emocao TEXT NOT NULL,
148
+ entrada TEXT NOT NULL,
149
+ resposta TEXT NOT NULL,
150
+ tom TEXT NOT NULL,
151
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
152
+ );
153
+ CREATE TABLE IF NOT EXISTS girias_aprendidas (
154
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
155
+ numero_usuario TEXT NOT NULL,
156
+ giria TEXT NOT NULL,
157
+ significado TEXT NOT NULL,
158
+ contexto TEXT,
159
+ frequencia INTEGER DEFAULT 1,
160
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
161
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
162
+ );
163
+ CREATE TABLE IF NOT EXISTS tom_usuario (
164
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
165
+ numero_usuario TEXT NOT NULL,
166
+ tom_detectado TEXT NOT NULL,
167
+ intensidade REAL DEFAULT 0.5,
168
+ contexto TEXT,
169
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
170
+ );
171
+ CREATE TABLE IF NOT EXISTS adaptacao_dinamica (
172
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
173
+ numero_usuario TEXT NOT NULL,
174
+ tipo_adaptacao TEXT NOT NULL,
175
+ valor_anterior TEXT,
176
+ valor_novo TEXT,
177
+ razao TEXT,
178
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
179
+ );
180
+ CREATE TABLE IF NOT EXISTS pronomes_por_tom (
181
+ tom TEXT PRIMARY KEY,
182
+ pronomes TEXT NOT NULL
183
+ );
184
+ CREATE TABLE IF NOT EXISTS contexto (
185
+ user_key TEXT PRIMARY KEY,
186
+ historico TEXT,
187
+ emocao_atual TEXT,
188
+ termos TEXT,
189
+ girias TEXT,
190
+ tom TEXT
191
+ );
192
+
193
+ -- TABELAS NOVAS (V22/V23)
194
+ CREATE TABLE IF NOT EXISTS abrev_usuarios (
195
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
196
+ numero_usuario TEXT NOT NULL,
197
+ abreviacao TEXT NOT NULL,
198
+ palavra_completa TEXT,
199
+ frequencia INTEGER DEFAULT 1
200
+ );
201
+
202
+ -- DADOS INICIAIS
203
+ INSERT OR IGNORE INTO pronomes_por_tom (tom, pronomes) VALUES
204
+ ('formal', 'Sr., ilustre, boss, maior, homem'),
205
+ ('rude', 'parvo, estúpido, burro, analfabeto, desperdício de esperma'),
206
+ ('casual', 'mano, puto, cota, mwangolé, kota'),
207
+ ('neutro', 'amigo, parceiro, camarada');
208
+ ''')
209
+ conn.commit()
210
+ logger.info(f"Banco V23 inicializado: {self.db_path}")
211
+
212
+ # ================================================================
213
+ # MIGRAÇÃO AUTOMÁTICA — GARANTE COLUNAS + ÍNDICES
214
+ # ================================================================
215
+ def _ensure_all_columns_and_indexes(self):
216
+ with self._get_connection() as conn:
217
+ c = conn.cursor()
218
+ # Migrações para embeddings
219
+ c.execute("PRAGMA table_info(embeddings)")
220
+ cols = {row[1] for row in c.fetchall()}
221
+ if 'numero_usuario' not in cols:
222
+ c.execute("ALTER TABLE embeddings ADD COLUMN numero_usuario TEXT")
223
+ if 'source_type' not in cols:
224
+ c.execute("ALTER TABLE embeddings ADD COLUMN source_type TEXT DEFAULT 'conversa'")
225
+ if 'texto' not in cols:
226
+ c.execute("ALTER TABLE embeddings ADD COLUMN texto TEXT DEFAULT ''")
227
+
228
+ # Índices
229
+ c.executescript('''
230
+ CREATE INDEX IF NOT EXISTS idx_mensagens_numero ON mensagens(numero);
231
+ CREATE INDEX IF NOT EXISTS idx_girias_usuario ON girias_aprendidas(numero_usuario);
232
+ CREATE INDEX IF NOT EXISTS idx_abrev_usuario ON abrev_usuarios(numero_usuario);
233
+ CREATE INDEX IF NOT EXISTS idx_aprendizados_usuario ON aprendizados(numero_usuario);
234
+ CREATE INDEX IF NOT EXISTS idx_aprendizado_detalhado_usuario ON aprendizado_detalhado(numero_usuario);
235
+ ''')
236
+ conn.commit()
237
+
238
+ # ================================================================
239
+ # MÉTODOS PRINCIPAIS (para app.py)
240
+ # ================================================================
241
+ def salvar_mensagem(self, usuario, mensagem, resposta, numero=None, is_reply=False, mensagem_original=None):
242
+ cols = ['usuario', 'mensagem', 'resposta']
243
+ vals = [usuario, mensagem, resposta]
244
+ if numero: cols.append('numero'); vals.append(numero)
245
+ if is_reply is not None: cols.append('is_reply'); vals.append(int(is_reply))
246
+ if mensagem_original: cols.append('mensagem_original'); vals.append(mensagem_original)
247
+ placeholders = ', '.join(['?' for _ in cols])
248
+ query = f"INSERT INTO mensagens ({', '.join(cols)}) VALUES ({placeholders})"
249
+ self._execute(query, tuple(vals), commit=True)
250
+
251
+ def carregar_contexto(self, numero: str) -> Dict[str, Any]:
252
+ row = self._execute("SELECT historico, girias, tom FROM contexto WHERE user_key=?", (numero,))
253
+ if row:
254
+ return {
255
+ "historico": json.loads(row[0][0] or "[]")[-8:],
256
+ "girias": json.loads(row[0][1] or "[]"),
257
+ "tom": row[0][2] or "neutro"
258
+ }
259
+ return {"historico": [], "girias": [], "tom": "neutro"}
260
+
261
+ def salvar_contexto(self, numero: str, historico: List, girias: List, tom: str):
262
+ self._execute(
263
+ "INSERT OR REPLACE INTO contexto (user_key, historico, girias, tom) VALUES (?, ?, ?, ?)",
264
+ (numero, json.dumps(historico), json.dumps(girias), tom), commit=True
265
+ )
266
+
267
+ def recuperar_girias_usuario(self, numero_usuario: str) -> List[str]:
268
+ rows = self._execute("SELECT giria FROM girias_aprendidas WHERE numero_usuario=?", (numero_usuario,))
269
+ return [r[0] for r in rows] if rows else []
270
+
271
+ def recuperar_abreviacoes_usuario(self, numero_usuario: str) -> Dict[str, str]:
272
+ rows = self._execute("SELECT abreviacao, palavra_completa FROM abrev_usuarios WHERE numero_usuario=?", (numero_usuario,))
273
+ return {r[0]: r[1] for r in rows} if rows else {}
274
+
275
+ def aprender_giria(self, numero: str, giria: str, significado: str = None):
276
+ row = self._execute("SELECT frequencia FROM girias_aprendidas WHERE numero_usuario=? AND giria=?", (numero, giria))
277
+ if row:
278
+ self._execute("UPDATE girias_aprendidas SET frequencia = frequencia + 1, updated_at = CURRENT_TIMESTAMP WHERE numero_usuario=? AND giria=?", (numero, giria), commit=True)
279
+ else:
280
+ self._execute("INSERT INTO girias_aprendidas (numero_usuario, giria, significado) VALUES (?, ?, ?)", (numero, giria, significado or ""), commit=True)
281
+
282
+ def aprender_abreviacao(self, numero: str, abrev: str, completa: str):
283
+ row = self._execute("SELECT frequencia FROM abrev_usuarios WHERE numero_usuario=? AND abreviacao=?", (numero, abrev))
284
+ if row:
285
+ self._execute("UPDATE abrev_usuarios SET frequencia = frequencia + 1 WHERE numero_usuario=? AND abreviacao=?", (numero, abrev), commit=True)
286
+ else:
287
+ self._execute("INSERT INTO abrev_usuarios (numero_usuario, abreviacao, palavra_completa) VALUES (?, ?, ?)", (numero, abrev, completa), commit=True)
288
+
289
+ def detectar_tom(self, numero: str, mensagem: str) -> str:
290
+ rude = any(w in mensagem.lower() for w in ["parvo", "burro", "merda"])
291
+ casual = any(w in mensagem.lower() for w in ["epá", "kandando", "bué", "kota"])
292
+ if rude: return "rude"
293
+ if casual: return "casual"
294
+ return "neutro"