Md.converter / app.py
Nahsof's picture
Create app.py
4d96f75 verified
Raw
History Blame Contribute Delete
7.39 kB
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())