232 lines
8.4 KiB
Python
232 lines
8.4 KiB
Python
import sqlite3
|
|
import json
|
|
import logging
|
|
from datetime import datetime
|
|
from typing import List, Dict, Any, Optional
|
|
import os
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class DatabaseManager:
|
|
"""
|
|
Gestiona la persistencia de resultados de preclasificación en una base de datos SQLite.
|
|
|
|
Estructura de tablas:
|
|
- runs: Registra cada ejecución global (fecha, ruta raíz, configuración).
|
|
- results: Almacena el estado de procesamiento de cada archivo individual.
|
|
- matches: Detalle de campos específicos encontrados en cada archivo.
|
|
"""
|
|
def __init__(self, db_path: str = "preclassification.db"):
|
|
self.db_path = db_path
|
|
self._init_db()
|
|
|
|
def _init_db(self):
|
|
"""
|
|
Inicializa el esquema de la base de datos si no existe.
|
|
Crea las tablas 'runs', 'results' y 'matches' con sus respectivas llaves foráneas.
|
|
"""
|
|
try:
|
|
conn = sqlite3.connect(self.db_path)
|
|
c = conn.cursor()
|
|
|
|
# Tabla de ejecuciones
|
|
c.execute('''
|
|
CREATE TABLE IF NOT EXISTS runs (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
timestamp TEXT NOT NULL,
|
|
root_path TEXT NOT NULL,
|
|
config TEXT
|
|
)
|
|
''')
|
|
|
|
# Tabla de resultados por archivo/radicado
|
|
c.execute('''
|
|
CREATE TABLE IF NOT EXISTS results (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
run_id INTEGER NOT NULL,
|
|
radicado TEXT NOT NULL,
|
|
file_path TEXT,
|
|
status TEXT NOT NULL, -- 'MATCH', 'NO_MATCH', 'ERROR'
|
|
raw_response TEXT,
|
|
FOREIGN KEY(run_id) REFERENCES runs(id)
|
|
)
|
|
''')
|
|
|
|
# Tabla de coincidencias detalladas (campo por campo)
|
|
c.execute('''
|
|
CREATE TABLE IF NOT EXISTS matches (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
result_id INTEGER NOT NULL,
|
|
key TEXT NOT NULL,
|
|
expected_value TEXT,
|
|
found_value TEXT,
|
|
exists_bool BOOLEAN NOT NULL,
|
|
FOREIGN KEY(result_id) REFERENCES results(id)
|
|
)
|
|
''')
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
except Exception as e:
|
|
logger.error(f"Error inicializando base de datos: {e}")
|
|
|
|
def create_run(self, root_path: str, config: Dict[str, Any]) -> int:
|
|
"""
|
|
Registra una nueva sesión de procesamiento.
|
|
|
|
Args:
|
|
root_path: Directorio raíz procesado.
|
|
config: Diccionario de configuración usado (se guarda como JSON).
|
|
|
|
Returns:
|
|
ID de la ejecución creada.
|
|
"""
|
|
conn = sqlite3.connect(self.db_path)
|
|
c = conn.cursor()
|
|
timestamp = datetime.now().isoformat()
|
|
c.execute('INSERT INTO runs (timestamp, root_path, config) VALUES (?, ?, ?)',
|
|
(timestamp, root_path, json.dumps(config)))
|
|
run_id = c.lastrowid
|
|
conn.commit()
|
|
conn.close()
|
|
return run_id
|
|
|
|
def add_result(self, run_id: int, radicado: str, file_path: str, status: str, raw_response: str) -> int:
|
|
"""
|
|
Añade el resultado del procesamiento de un archivo.
|
|
|
|
Args:
|
|
run_id: ID de la ejecución padre.
|
|
radicado: Identificador del radicado.
|
|
file_path: Ruta al archivo físico.
|
|
status: Estado final ('MATCH', 'NO_MATCH', 'ERROR').
|
|
raw_response: Respuesta completa del modelo.
|
|
|
|
Returns:
|
|
ID del resultado insertado.
|
|
"""
|
|
conn = sqlite3.connect(self.db_path)
|
|
c = conn.cursor()
|
|
c.execute('INSERT INTO results (run_id, radicado, file_path, status, raw_response) VALUES (?, ?, ?, ?, ?)',
|
|
(run_id, radicado, file_path, status, raw_response))
|
|
result_id = c.lastrowid
|
|
conn.commit()
|
|
conn.close()
|
|
return result_id
|
|
|
|
def add_match(self, result_id: int, key: str, expected_value: Optional[str], found_value: Optional[str], exists: bool):
|
|
"""
|
|
Registra el hallazgo (o ausencia) de un campo específico en un documento.
|
|
|
|
Args:
|
|
result_id: ID del resultado al que pertenece.
|
|
key: Nombre del campo/llave.
|
|
expected_value: Valor que se esperaba encontrar (si aplica).
|
|
found_value: Valor extraído por el modelo.
|
|
exists: Booleano que indica si el campo fue efectivamente localizado.
|
|
"""
|
|
conn = sqlite3.connect(self.db_path)
|
|
c = conn.cursor()
|
|
c.execute('INSERT INTO matches (result_id, key, expected_value, found_value, exists_bool) VALUES (?, ?, ?, ?, ?)',
|
|
(result_id, key, expected_value, found_value, exists))
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
def get_run_results(self, run_id: int) -> List[Dict[str, Any]]:
|
|
"""Obtiene resultados completos de una ejecución."""
|
|
conn = sqlite3.connect(self.db_path)
|
|
conn.row_factory = sqlite3.Row
|
|
c = conn.cursor()
|
|
|
|
c.execute('''
|
|
SELECT r.radicado, r.file_path, r.status, m.key, m.expected_value, m.found_value, m.exists_bool
|
|
FROM results r
|
|
LEFT JOIN matches m ON r.id = m.result_id
|
|
WHERE r.run_id = ?
|
|
''', (run_id,))
|
|
|
|
rows = c.fetchall()
|
|
conn.close()
|
|
return [dict(row) for row in rows]
|
|
|
|
def get_all_runs(self) -> List[Dict[str, Any]]:
|
|
"""Obtiene lista de todos los runs ordenados por fecha descendente."""
|
|
conn = sqlite3.connect(self.db_path)
|
|
conn.row_factory = sqlite3.Row
|
|
c = conn.cursor()
|
|
c.execute("SELECT * FROM runs ORDER BY timestamp DESC")
|
|
rows = c.fetchall()
|
|
conn.close()
|
|
return [dict(row) for row in rows]
|
|
|
|
def search_results(self, run_id: Optional[int] = None, status: Optional[str] = None, text: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
"""Busca resultados con filtros."""
|
|
conn = sqlite3.connect(self.db_path)
|
|
conn.row_factory = sqlite3.Row
|
|
c = conn.cursor()
|
|
|
|
query = '''
|
|
SELECT r.id, r.run_id, r.radicado, r.file_path, r.status, r.raw_response, runs.timestamp
|
|
FROM results r
|
|
JOIN runs ON r.run_id = runs.id
|
|
WHERE 1=1
|
|
'''
|
|
params = []
|
|
|
|
if run_id:
|
|
query += " AND r.run_id = ?"
|
|
params.append(run_id)
|
|
|
|
if status and status != "Todos":
|
|
query += " AND r.status = ?"
|
|
params.append(status)
|
|
|
|
if text:
|
|
# Busqueda simple en radicado o path
|
|
query += " AND (r.radicado LIKE ? OR r.file_path LIKE ?)"
|
|
wild = f"%{text}%"
|
|
params.extend([wild, wild])
|
|
|
|
query += " ORDER BY r.id DESC LIMIT 1000"
|
|
|
|
c.execute(query, params)
|
|
rows = c.fetchall()
|
|
conn.close()
|
|
return [dict(row) for row in rows]
|
|
|
|
def update_result_status(self, result_id: int, status: str) -> None:
|
|
"""Actualiza el status de un resultado existente."""
|
|
conn = sqlite3.connect(self.db_path)
|
|
c = conn.cursor()
|
|
c.execute("UPDATE results SET status = ? WHERE id = ?", (status, result_id))
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
def get_matches_for_result(self, result_id: int) -> List[Dict[str, Any]]:
|
|
"""Obtiene el detalle de matches para un resultado específico."""
|
|
conn = sqlite3.connect(self.db_path)
|
|
conn.row_factory = sqlite3.Row
|
|
c = conn.cursor()
|
|
c.execute("SELECT * FROM matches WHERE result_id = ?", (result_id,))
|
|
rows = c.fetchall()
|
|
conn.close()
|
|
return [dict(row) for row in rows]
|
|
|
|
def get_run(self, run_id: int) -> Optional[Dict[str, Any]]:
|
|
"""Obtiene un run por ID (incluye config como dict si aplica)."""
|
|
conn = sqlite3.connect(self.db_path)
|
|
conn.row_factory = sqlite3.Row
|
|
c = conn.cursor()
|
|
c.execute("SELECT * FROM runs WHERE id = ?", (run_id,))
|
|
row = c.fetchone()
|
|
conn.close()
|
|
if not row:
|
|
return None
|
|
d = dict(row)
|
|
try:
|
|
cfg_raw = d.get("config")
|
|
d["config"] = json.loads(cfg_raw) if cfg_raw else {}
|
|
except Exception:
|
|
d["config"] = {}
|
|
return d
|