File size: 7,391 Bytes
4d96f75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
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'(?<!\n)\n(?!\n)', ' ', text)
    # Purgar espacios múltiples
    text = re.sub(r'[ \t]+', ' ', text)
    
    # 3. SANACIÓN CIENTÍFICO-MATEMÁTICA UNIVERSAL
    # Potencias (ej. 104 -> 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())