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: def __init__(self, db_path: str = "preclassification.db"): self.db_path = db_path self._init_db() def _init_db(self): """Inicializa las tablas si no existen.""" 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: """Crea una nueva ejecución y devuelve su ID.""" 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 un resultado de archivo procesado y devuelve su ID.""" 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): """Añade el detalle de un campo encontrado (o no).""" 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 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