import gradio as gr import re import tempfile import os # Importaciones de Docling (IA de Layout espacial y Visión) from docling.document_converter import DocumentConverter from docling.datamodel.document import DocItemLabel def heal_and_flatten_medical_text(text): """ Motor NLP de Sanación Semántica y Aplanamiento. Diseñado para cualquier libro médico/biológico universitario. Convierte el texto crudo en vectores puros para ingesta en RAG. """ if not text: return "" # 1. PURGA DE REFERENCIAS FANTASMAS # Elimina referencias cruzadas a figuras y tablas que la IA ya borró. text = re.sub(r'\(\s*[vV]?\.?\s*(?:fig\.|figs\.|tabla|tablas|cuadro|cuadros)\s*\d+[-.]\d+[^\)]*\)', '', text, flags=re.IGNORECASE) text = re.sub(r'\b(?:véase|ver)\s+(?:figura|tabla|cuadro)\s+\d+[-.]\d+\b', '', text, flags=re.IGNORECASE) # 2. APLANAMIENTO Y COSTURA DE COLUMNAS (Flattening & De-hyphenation) # Docling a veces deja guiones al unir columnas; aquí los forzamos a unirse. text = re.sub(r'(\w+)-\s*\n\s*(\w+)', r'\1\2', text) # Destruimos los saltos de línea internos: convertimos párrafos-bloque en cadenas lineales continuas. text = re.sub(r'(? 10^4) cuando están junto a palabras de biología/química. text = re.sub(r'\b10(3|4|5|6|7|8|9|10|11|12|13)\b(?=\s*(?:y|a|células|linfocitos|macrófagos|bacterias|virus|copias|moléculas|mg|ml|µl|µm|mm|pg|ng))', r'10^\1', text, flags=re.IGNORECASE) text = re.sub(r'\b1\s*[xX]\s*10(\d{1,2})\b', r'1 x 10^\1', text) # Corrección de Unidades de Medida corrompidas por OCR text = re.sub(r'\|xm', 'µm', text) text = re.sub(r'\bpm\b(?=\s)', 'µm', text) text = re.sub(r'\[iL', 'µL', text) # 4. GRECISMOS Y NOMENCLATURA INMUNOLÓGICA (Abbas, Harrison, Robbins) text = re.sub(r'\ba4p7\b', 'α4β7', text) text = re.sub(r'\baLp2\b', 'αLβ2', text) text = re.sub(r'\ba\(3\b', 'αβ', text) text = re.sub(r'\bLT\(3\b', 'LTβ', text) text = re.sub(r'\bIFN-7\b', 'IFN-γ', text) text = re.sub(r'\bIL-ip\b', 'IL-1β', text) text = re.sub(r'\bCD(\d+)\s*\+', r'CD\1+', text) # Ejemplo: CD8 + -> CD8+ return text.strip() def process_medical_pdf(pdf_file, progress=gr.Progress()): """ Controlador Orquestador. Administra la conversión de Docling, aplica los filtros topológicos y ensambla el archivo Markdown puro para Gemini/NotebookLM. """ if pdf_file is None: raise gr.Error("Debes subir un archivo PDF.") try: # FASE A: Ingesta Visual y Comprensión Geométrica (Docling) progress(0.1, desc="👁️ Fase 1/3: Iniciando IA de Visión (Cargando modelos locales)...") converter = DocumentConverter() progress(0.2, desc="📐 Analizando topología y dobles columnas del PDF (Puede tardar)...") doc_result = converter.convert(pdf_file.name) document = doc_result.document md_blocks = [] # Iterador de elementos detectados por la IA elements = list(document.iterate_items()) total_elements = len(elements) # FASE B & C: Triaje Topológico y Sanación progress(0.5, desc="🧹 Fase 2/3: Purgando tablas, imágenes y ruido editorial...") for idx, (item, level) in enumerate(elements): if idx % 10 == 0: # Actualización de UI progresiva progress(0.5 + (0.4 * (idx / total_elements))) # REGLA DE EXCLUSIÓN RADICAL: Ignorar ruido visual y tabular if item.label in[ DocItemLabel.PAGE_HEADER, DocItemLabel.PAGE_FOOTER, DocItemLabel.TABLE, DocItemLabel.PICTURE, DocItemLabel.FORMULA, DocItemLabel.CAPTION ]: continue # REGLA DE PRESERVACIÓN Y SANACIÓN (Preparación RAG) if item.label in[DocItemLabel.SECTION_HEADER, DocItemLabel.TITLE]: clean_text = heal_and_flatten_medical_text(item.text) if clean_text: # Garantiza que el nivel del encabezado sea entre # y ###### heading_level = min(max(level, 1), 6) md_blocks.append(f"{'#' * heading_level} {clean_text}") elif item.label in [DocItemLabel.PARAGRAPH, DocItemLabel.TEXT]: clean_text = heal_and_flatten_medical_text(item.text) if clean_text: md_blocks.append(clean_text) elif item.label == DocItemLabel.LIST_ITEM: clean_text = heal_and_flatten_medical_text(item.text) if clean_text: md_blocks.append(f"* {clean_text}") # FASE D: Ensamblaje Estructural para Gemini/NotebookLM progress(0.95, desc="📦 Fase 3/3: Ensamblando archivo Markdown optimizado...") # Une todos los bloques con exactamente DOS saltos de línea # Esto le indica al LLM dónde termina exactamente un chunk semántico. final_md = "\n\n".join(md_blocks) # Generar archivo descargable fd, path = tempfile.mkstemp(suffix=".md", text=True) with os.fdopen(fd, 'w', encoding='utf-8') as f: f.write(final_md) progress(1.0, desc="✅ ¡Proceso completado con éxito!") return path except Exception as e: raise gr.Error(f"Error crítico en el pipeline: {str(e)}") # ------------------------------------------------------------------------- # INTERFAZ DE USUARIO (GRADIO 6.0+) # ------------------------------------------------------------------------- with gr.Blocks() as app: gr.Markdown( """ # 🧠 Generador RAG: PDF Médico a `.md` Optimizado **Pipeline ETL de Grado Ingeniería:** Este sistema NO usa APIs externas. Descarga modelos de IA Espacial locales para deducir dobles columnas, **purga el 100% de tablas, imágenes, captions y marcas de agua**, y aplana los párrafos sanando anomalías ópticas. Genera un archivo Markdown puro, diseñado **exclusivamente para ingesta en Gemini 3.1 Pro, NotebookLM y bases de datos vectoriales**. """ ) with gr.Row(): with gr.Column(): gr.Markdown("### 1. Subir Libro Académico") input_file = gr.File(label="Sube tu archivo (.PDF)", file_types=[".pdf"]) process_btn = gr.Button("🚀 Extraer y Generar Markdown", variant="primary") gr.Markdown("*Nota: Este proceso ocurre localmente. Puede tomar varios minutos si el PDF tiene cientos de páginas.*") with gr.Column(): gr.Markdown("### 2. Descargar Dataset") output_file = gr.File(label="Archivo Optimizado para IA (.md)", interactive=False) # Conexión del orquestador process_btn.click( fn=process_medical_pdf, inputs=[input_file], outputs=[output_file] ) if __name__ == "__main__": app.launch(theme=gr.themes.Soft())