Files
IPSExtractor/docs

IPSExtractor — Documentación técnica (Desarrolladores)

Este documento explica la arquitectura, módulos, flujos, modelo de datos/CSV, estados de la GUI, configuración y extensibilidad del proyecto. Contiene diagramas Mermaid incrustados.

Índice

Visión general

La aplicación procesa carpetas con “radicados” que contienen PDFs/TIFFs de facturas. Orquesta la conversión de documentos a imágenes Base64, llama a una estrategia de extracción para obtener un JSON, normaliza el resultado a un esquema seleccionado por el usuario y exporta CSV incremental. Incluye GUI con selección interactiva de campos y estimación de costos por tokens.

Arquitectura de componentes

flowchart LR
  subgraph GUI
    V[gui/view.py]
    C[gui/controller.py]
    FS[gui/field_selector.py]
  end

  subgraph APP[application/*]
    S[service.py]
    STG[strategies.py<br/>(InvoiceExtractorStrategy)]
    NRM[normalizer.py]
    PR[profiles.py]
    PB[prompts.py]
    CFG[config.py]
  end

  subgraph CORE
    PDF[core/pdf_utils.py]
    TXT[core/text_utils.py]
    CSV[core/csv_utils.py]
    UT[core/utils.py]
  end

  V --> C
  C --> S
  C --> FS
  S --> STG
  S --> NRM
  S --> PR
  S --> PB
  S --> CFG
  S --> PDF
  S --> TXT
  S --> CSV
  S --> UT

  S --> LOG[(logs/*)]

Módulos y responsabilidades

  • application/service.py: orquestación principal (extracción con fallback, schema inicial, normalización, CSV incremental, estimación de costos/tokens).
    • Funciones clave:
      • extract_full_from_pdf(pdf_path, instruction?): conversión a imágenes, llamada a extractor, parseo JSON, fallback DPI/páginas.
      • build_schema_from_first(pdf_path, instruction?): genera schema a partir del primer documento.
      • build_schema_from_first_for_types(root_path, doc_types): genera schema_by_type usando el primer radicado y la primera coincidencia por keyword de cada tipo.
      • process_root_with_field_selection(root_path, keywords|doc_types, fields?, ...): flujo por radicado; soporta multi-documento; selección de campos; normalización y CSV incremental.
      • estimate_tokens_and_cost(root_path, keywords|doc_types, ...): simulación con primer radicado; en multi suma por tipo.
  • application/profiles.py: perfiles por tipo de documento e instrucciones por defecto.
  • application/strategies.py: interfaz/estrategia de extracción (InvoiceExtractorStrategy).
  • application/normalizer.py: normalize_to_schema(...) para ajustar el JSON al esquema elegido.
  • application/prompts.py: PromptBuilder.build(...) para prompts reducidos según campos seleccionados.
  • application/config.py: settings (imaging, hilos, DPI, etc.).
  • core/pdf_utils.py: conversión PDF/TIFF → imágenes Base64.
  • core/text_utils.py: extract_json_from_text, build_schema.
  • core/csv_utils.py: aplanado de campos no-lista, detección de la primera lista de ítems, agregaciones y encabezados.
  • core/utils.py: utilidades (p. ej., collect_radicado_docs).
  • gui/controller.py: coordina GUI ↔ servicio, selector de campos y callbacks de progreso/uso.
  • gui/field_selector.py: diálogo de selección de paths y alias (valida duplicados y permite reproceso). En modo multi, agrupa visualmente por Tipo (p. ej., Factura/..., Remision/...).
  • gui/view.py: añade lista dinámica de Tipos de Documento (Nombre Lógico + Keyword).
  • gui/view.py: componentes visuales y actualizaciones (KPIs, barras, líneas de costo, etc.).

Flujos de ejecución

Incluye: disparo desde GUI, descubrimiento de radicados, creación de schema, selección de campos/alias, estimación y confirmación, extracción por documento con fallback, normalización y exportación incremental a CSV.

Secuencia general (GUI → Service → Normalización → CSV)

sequenceDiagram
  autonumber
  actor U as Usuario
  participant V as gui/view.py
  participant C as gui/controller.py
  participant S as application/service.py
  participant FS as gui/field_selector.py
  participant ST as application/strategies.py
  participant N as application/normalizer.py
  participant CSV as core/csv_utils.py
  participant TXT as core/text_utils.py
  participant UT as core/utils.py

  U->>V: Click "Procesar"
  V->>C: on_process()
  note over C: Lee root, keywords, fields opcionales
  C->>S: process_root_with_field_selection(fields=None, doc_types?)
  S->>UT: collect_radicado_docs()
  S->>S: build_schema_from_first(first.pdf)
  S->>TXT: extract_json_from_text(raw)
  S-->>C: [{ _needs_fields: true, schema, first_full }]
  C->>FS: Abrir diálogo de selección (schema + sample)
  FS-->>C: on_submit(selected_paths, alias_map)  Note: paths con prefijo de tipo `Tipo/Subruta`
  C->>S: estimate_tokens_and_cost(..., doc_types?, schema_by_type?, selected_paths?)
  C->>U: Confirmación (costos estimados)
  U-->>C: Aceptar
  C->>S: process_root_with_field_selection(fields=selected_fields, alias_map)
  loop Por cada radicado
    alt Multi-documento
      loop Por cada Tipo
        S->>ST: extract(images, prompt reducido por Tipo)
        S->>N: normalize_to_schema(data, paths del Tipo)
      end
      S-->>S: fusiona por Tipo => fused_data = {'radicado': R, 'Factura': {...}, 'Remision': {...}}
      S->>CSV: rows_from_data(fused_data, allowed_paths con prefijo)
    else Single-documento
      S->>ST: extract(images, prompt reducido)
      S->>N: normalize_to_schema(data, paths)
      S->>CSV: rows_from_data(data, allowed_paths, aggregate_list_paths?)
    end
    S->>S: _append_rows_to_csv(...)
    S-->>C: on_usage(phase, radicado, usage, cost)
    C->>V: Actualiza KPIs/barras/líneas
  end
  S-->>C: resultados finales (JSON)
  C->>V: Imprime resultados en Output

Secuencia de extracción con fallback

sequenceDiagram
  autonumber
  participant S as application/service.py
  participant PDF as core/pdf_utils.py
  participant ST as application/strategies.py
  participant TXT as core/text_utils.py

  S->>PDF: document_to_images_bs64(pdf, dpi=config, threads)
  S->>ST: extract(images_b64, instruction)
  alt Éxito
    ST-->>S: {raw, usage}
  else Error/Timeout/Payload
    S->>PDF: document_to_images_bs64(pdf, dpi=fallback, pages<=5)
    S->>ST: extract(images_b64_fb, instruction)
    ST-->>S: {raw, usage}
  end
  S->>TXT: extract_json_from_text(raw)
  TXT-->>S: json_text
  S-->>S: json.loads(json_text) => data

Modelo de datos y CSV

Describe cómo se aplanan campos no-lista, cómo se detecta la primera lista de ítems, cómo se agregan listas a columnas y cómo allowed_paths y los alias determinan el encabezado y las filas del CSV.

Diagrama de datos

classDiagram
  class ExtractedData {
    +string radicado
    +dict ...campos_no_lista
    +Item[] items
    +string _source_pdf
  }
  class Item {
    +string Codigo
    +int Cantidad
    +float V/Unitario
    +... otros subcampos anidados
  }
  ExtractedData "1" --> "0..*" Item : items

Notas:

  • Los “campos no lista” se aplanan con separador /, por ejemplo: Paciente/Nombre, Factura/Fecha.
  • La primera lista de objetos detectada se considera “lista de ítems principal” y se expande en columnas.

Mapeo a CSV (core/csv_utils.py)

  • Aplanado no-lista: flatten_non_list_fields(data) produce columnas para todas las hojas escalar (excluye listas).
  • Detección de lista principal: _find_first_items_list(data) devuelve items_key y items.
  • Subcolumnas de ítems: se aplanan como items_key/*/subruta, por ejemplo: items/*/V/Unitario.
  • Filtrado por allowed_paths (selección del usuario): solo se mantienen rutas de esa lista (más radicado siempre).
  • Agregación opcional: aggregate_list_paths=[base_path] genera columnas base_path/*/subclave con valores unidos por coma (para listas no principales).
  • Encabezado final: ['radicado', ...campos_no_lista, ...campos_items] respetando allowed_paths y orden alfabético de subrutas.
  • Alias: el alias_map de la GUI renombra columnas en el CSV final (encabezado y filas).

Ejemplos de columnas resultantes:

  • radicado
  • Factura/Número
  • Paciente/Nombre
  • items/*/Codigo
  • items/*/V/Unitario

Estados de la GUI y manejo de errores

Estados principales: Idle → Processing → (NeedsFields) → Estimating → Confirming → Processing2 → Done/Error. La extracción tiene fallback controlado y los errores se registran y opcionalmente se reflejan como filas de error en el CSV.

stateDiagram-v2
  [*] --> Idle
  Idle --> Processing: Click "Procesar"
  Processing --> NeedsFields: Service devuelve _needs_fields
  NeedsFields --> Estimating: on_submit(selected_fields, alias_map)
  Estimating --> Confirming: Mostrar costos estimados
  Confirming --> Processing2: Usuario acepta
  Confirming --> Idle: Usuario cancela
  Processing2 --> Done: Todos los radicados OK
  Processing2 --> Error: Excepción por radicado
  Done --> Idle
  Error --> Idle

Manejo de errores/fallback:

  • Extracción: si falla la primera llamada (timeout/payload), se reprocesa con DPI reducido y límite de páginas (≤5).
  • Escritura CSV: filas de error con radicado, _source_pdf, error cuando hay excepción.
  • Logs: se emiten logger.info/debug/exception con contexto (pdf, radicado, métricas de tiempo y tokens).
  • GUI: mantiene KPIs y barreras por fase; errores no bloquean el procesamiento de los siguientes radicados.

Configuración y ejecución

Parámetros clave en application/config.py:

  • settings.imaging.pdf_dpi: DPI para rasterizar PDF/TIFF (impacta calidad/tamaño).
  • settings.imaging.pdf_thread_count: paralelismo al generar imágenes.
  • settings.imaging.tiff_pages: páginas a considerar por defecto en TIFF multi-página.

Ejecución (GUI):

  1. Inicie la aplicación GUI.
  2. Seleccione la carpeta raíz de radicados (cada radicado contiene PDFs/TIFFs).
  3. Opcional: defina palabras clave de búsqueda (por defecto: Factura_O_Documento_Equivalente, factura).
  4. Opcional: defina campos a extraer (si se deja vacío se abrirá el selector).
  5. Opcional: elija ruta de salida para el CSV.
  6. Presione “Procesar”.

Exportación CSV:

  • Si se especifica csv_output_path, el servicio añadirá filas incrementalmente conforme procesa cada documento.
  • Los encabezados se generan en la primera escritura; si existe un CSV previo con contenido, se preserva su encabezado.

Extensibilidad

Nuevos perfiles, estrategias de extracción o ajustes en normalización y costos se pueden incorporar con cambios locales y mínimos acoplamientos.

  • Perfiles (application/profiles.py): agregue una entrada en PROFILES definiendo default_instruction, aggregate_list_paths, etc.
  • Estrategias de extracción (application/strategies.py): implemente InvoiceExtractorStrategy.extract(images_b64, instruction) para soportar nuevos backends/LLMs.
  • Normalización (application/normalizer.py): adapte normalize_to_schema para modelos y políticas de mapeo; los costos por fase se calculan en el servicio.
  • Prompts (application/prompts.py): PromptBuilder.build(selected_fields, schema) puede ajustarse para controlar la precisión y tamaño del prompt.
  • Estimación de costos (application/service.py::estimate_tokens_and_cost): modifique tarifas/modelos según su despliegue.

Preferencia de diseño:

  • Siempre que sea posible, se recomienda ajustar el prompt para conseguir un JSON estable en vez de depender de mapeos/alias complejos en código memory:10659104.

Registro y métricas

La app registra progreso, tiempos y costos por fase y por documento. Los logs se escriben en logs/* y la GUI muestra KPIs agregados.