Metadata-Version: 2.5
Name: betl
Version: 0.1.8
Summary: Una descripción corta de tu librería
Project-URL: Homepage, https://github.com
License: MIT
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.8
Requires-Dist: ddddocr>=1.6.1
Requires-Dist: numpy<2.4.0,>=1.24
Requires-Dist: opencv-contrib-python>=4.0.0
Requires-Dist: opencv-python>=4.0.0
Requires-Dist: paddleocr>=3.7.0
Requires-Dist: paddlepaddle>=3.3.1
Requires-Dist: pydantic>=2.0.0
Requires-Dist: pymupdf>=1.24.0
Requires-Dist: requests>=2.30.0
Description-Content-Type: text/markdown

# main.py
import logging
import sys
from pathlib import Path
import importlib.util
from typing import Any


RAIZ_PROYECTO = Path(__file__).parent
sys.path.append(str(RAIZ_PROYECTO))
sys.path.append(str(RAIZ_PROYECTO / "src"))

# Importamos tu pipeline optimizado por densidad NumPy
from core.pipeline import ETLDocumentPipeline

# Configuración limpia de logs en consola
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
    handlers=[logging.StreamHandler(sys.stdout)]
)
logger = logging.getLogger("Main")


def cargar_clase_contrato_judicial(ruta_contrato: Path) -> Any:
    """
    Carga dinámicamente el archivo contrato.py desde el disco D: 
    y extrae la clase decorada lista para ser procesada por el Core.
    """
    if not ruta_contrato.exists():
        raise FileNotFoundError(f"No se encontró el archivo de contrato en: {ruta_contrato}")
    
    # Proceso oficial de importación dinámica en tiempo de ejecución
    spec = importlib.util.spec_from_file_location("contrato_modulo", str(ruta_contrato))
    modulo = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(modulo)
    
    # 1. Intento de carga directa por el nombre explícito definido
    if hasattr(modulo, "MiContratoRemate"):
        clase_contrato = getattr(modulo, "MiContratoRemate")
        logger.info("[Contrato] Clase 'MiContratoRemate' localizada e importada con éxito.")
        return clase_contrato
        
    # 2. Fallback de barrido reflexivo en caso de que cambies el nombre de la clase
    for atributo_nombre in dir(modulo):
        atributo = getattr(modulo, atributo_nombre)
        # Buscamos clases que tengan el atributo inyectado por tu decorador @contract
        if isinstance(atributo, type) and hasattr(atributo, "_extraction_contract"):
            logger.info(f"[Contrato] Detectada clase con contrato declarativo válido: '{atributo_nombre}'")
            return atributo
                
    raise AttributeError("No se localizó ninguna clase decorada con @contract en contrato.py")


def main():
    # Parámetros e infraestructura de rutas fijas solicitadas
    ruta_pdf = Path(r"d:\betl\testpdf.pdf")
    ruta_contrato = Path(r"d:\betl\contrato.py")
    
    logger.info("=== INICIANDO ENTORNO DE PRUEBAS DE INFRAESTRUCTURA ETL ===")
    
    try:
        # 1. Extraer la Clase del Contrato (sin instanciar con paréntesis)
        contrato_clase = cargar_clase_contrato_judicial(ruta_contrato)
        
        # 2. Inicializar el Pipeline Persistente (Carga pesada de memoria en caché una única vez)
        pipeline = ETLDocumentPipeline(
            modelo_llm="qwen3:8b", 
            timeout_llm=250, 
            chunk_size=8000,
            ocr_lang="es"
        )
        
        # 3. Lanzar procesamiento automático (OCR Regional + Densidad Vectorizada por NumPy)
        logger.info(f"[Pipeline] Enviando documento {ruta_pdf.name} al motor analítico...")
        resultado_final = pipeline.ejecutar(
            pdf_path=ruta_pdf,
            contrato=contrato_clase,  # Pasamos la clase directamente al orquestador
            captcha_path=None,
            modo_pdf="auto"           # Automatización por análisis de píxeles activa
        )
        
        # 4. Impresión estructurada del DTO procesado
        if resultado_final:
            logger.info("¡Pipeline ETL procesado y consolidado con éxito!")
            print("\n================ DTO ESTRUCTURADO FINAL (FUSIÓN) ================")
            import json
            print(json.dumps(resultado_final, indent=4, ensure_ascii=False))
            print("=================================================================\n")
        else:
            logger.error("[Pipeline] El flujo terminó pero el MergerService o el OCR devolvieron vacío.")
            
    except Exception as e:
        logger.error(f"Error crítico abortivo en el punto de entrada principal: {e}", exc_info=True)


if __name__ == "__main__":
    main()
