271 lines
12 KiB
Markdown
271 lines
12 KiB
Markdown
# 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](#visión-general)
|
|
- [Arquitectura de componentes](#arquitectura-de-componentes)
|
|
- [Módulos y responsabilidades](#módulos-y-responsabilidades)
|
|
- [Flujos de ejecución](#flujos-de-ejecución)
|
|
- [Modelo de datos y CSV](#modelo-de-datos-y-csv)
|
|
- [Estados de la GUI y manejo de errores](#estados-de-la-gui-y-manejo-de-errores)
|
|
- [Configuración y ejecución](#configuración-y-ejecución)
|
|
- [Extensibilidad](#extensibilidad)
|
|
- [Registro y métricas](#registro-y-métricas)
|
|
|
|
## 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
|
|
|
|
```mermaid
|
|
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)
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```mermaid
|
|
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.
|
|
|
|
```mermaid
|
|
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.
|
|
|
|
|