## IPSExtractor Microservices Monorepo con servicios separados como si fueran repos independientes. Cada servicio tiene su propia API, dependencias, Dockerfile, codigo de dominio e infraestructura local. No hay paquetes compartidos entre servicios. ```text services/ extraction/ Repo/servicio de extraccion api/ API FastAPI domain/ Dominio propio de extraccion y normalizacion core/ Utilidades propias para PDF, texto y filas infrastructure/ Persistencia propia del servicio requirements.txt Dockerfile cleaning/ Repo/servicio de limpieza api/ API FastAPI domain/ Reglas propias de limpieza infrastructure/ Persistencia propia del servicio requirements.txt Dockerfile api_gateway/ API central opcional api/ Proxy HTTP hacia extraction y cleaning requirements.txt Dockerfile desktop_app/ App desktop Windows/Mac electron/ Main process, preload y dialogos nativos src/ React UI, cliente API y export CSV package.json ``` La base de datos Postgres es compartida como contrato de integracion, pero el codigo de acceso a datos esta duplicado dentro de cada servicio para que puedan vivir por separado. ### Puertos | Servicio | Puerto | Uso | | --- | ---: | --- | | `api_gateway` | `8000` | API central opcional | | `extraction` | `8001` | API propia de extraccion | | `cleaning` | `8002` | API propia de limpieza | | `database` | `5432` | Postgres | | `desktop_app` | local | App desktop fuera de Docker | Documentacion interactiva: - `http://localhost:8000/docs` - `http://localhost:8001/docs` - `http://localhost:8002/docs` ### Variables ```env IPS_DOCUMENT_ROOT=C:/ruta/a/documentos # Ejemplo: extraccion con Grok/xAI EXTRACTOR_PROVIDER=xai EXTRACTOR_BASE_URL=https://api.x.ai/v1 EXTRACTOR_API_KEY=tu_xai_api_key EXTRACTOR_MODEL=grok-tu-modelo EXTRACTOR_TIMEOUT_S=120 # Ejemplo: normalizacion con OpenAI NORMALIZER_PROVIDER=openai NORMALIZER_BASE_URL=https://api.openai.com/v1 NORMALIZER_API_KEY=tu_openai_api_key NORMALIZER_MODEL=gpt-tu-modelo NORMALIZER_TIMEOUT_S=120 PDF_DPI=600 PDF_THREAD_COUNT=10 CONCURRENCY_LIMIT=4 ALLOWED_DOCUMENT_TYPES=CC,TI,CE,RC,PA,PPT,PEP,EPT,NUIP ``` Si `NORMALIZER_BASE_URL` o `NORMALIZER_API_KEY` se dejan vacios, el servicio usa el mismo backend configurado en `EXTRACTOR_BASE_URL` y `EXTRACTOR_API_KEY`. Dentro de Docker, `IPS_DOCUMENT_ROOT` se monta como `/data`. ### Levantar ```bash docker compose up --build ``` Health checks: ```bash curl http://localhost:8000/health curl http://localhost:8001/health curl http://localhost:8002/health ``` ### Flujo Directo Por Servicio 1. Ver modelos disponibles en extraccion: ```bash curl http://localhost:8001/models ``` 2. Descubrir campos: ```bash curl -X POST http://localhost:8001/schemas \ -H "Content-Type: application/json" \ -d '{"document_path": "/data/radicado-1/factura.pdf"}' ``` 3. Ejecutar extraccion: ```bash curl -X POST http://localhost:8001/batches \ -H "Content-Type: application/json" \ -d '{ "root_path": "/data", "keywords": ["factura"], "fields": ["numero_factura", "items/*/codigo", "items/*/valor"], "alias_map": { "numero_factura": "factura", "items/*/codigo": "codigo", "items/*/valor": "valor" }, "structure_mode": "radicados" }' ``` La respuesta devuelve `id`; ese valor es el `batch_id`. 4. Consultar filas extraidas: ```bash curl http://localhost:8001/batches/{batch_id}/rows ``` 5. Ver reglas de limpieza: ```bash curl http://localhost:8002/rules ``` 6. Analizar columnas extraidas: ```bash curl http://localhost:8002/batches/{batch_id}/analysis ``` 7. Ejecutar limpieza: ```bash curl -X POST http://localhost:8002/jobs \ -H "Content-Type: application/json" \ -d '{ "extraction_batch_id": "{batch_id}", "rules_per_column": { "valor": ["to_integer"], "fecha": ["date_to_ddmmyyyy"] } }' ``` La respuesta devuelve `id`; ese valor es el `job_id`. 8. Consultar filas limpias: ```bash curl http://localhost:8002/jobs/{job_id}/rows ``` ### Flujo Por API Gateway El gateway no importa codigo interno de los servicios; solo enruta HTTP. ```bash curl http://localhost:8000/models curl http://localhost:8000/config curl http://localhost:8000/extraction/batches/{batch_id}/rows curl http://localhost:8000/cleaning/jobs/{job_id}/rows ``` Para llamar cualquier endpoint propio de un servicio: - `http://localhost:8000/extraction/...` - `http://localhost:8000/cleaning/...` ### Desarrollo Independiente Extraccion como repo aparte: ```bash cd services/extraction pip install -r requirements.txt uvicorn api.main:app --host 0.0.0.0 --port 8001 ``` Limpieza como repo aparte: ```bash cd services/cleaning pip install -r requirements.txt uvicorn api.main:app --host 0.0.0.0 --port 8002 ``` Gateway como repo aparte: ```bash cd services/api_gateway pip install -r requirements.txt uvicorn api.main:app --host 0.0.0.0 --port 8000 ``` App desktop como repo aparte: ```bash cd services/desktop_app npm install npm run dev ``` La app desktop usa `http://localhost:8000` por defecto. **No monta la carpeta en Docker**: indexa los documentos localmente y los sube uno a uno al API (`POST /extraction/schemas/upload` y `POST /tasks/{id}/ingest`). Solo se persisten las filas extraidas en Postgres; los PDFs permanecen en el equipo del usuario. ```bash cd services/desktop_app npm install npm run dev ``` #### Flujo desktop (sin volumen `/data`) 1. Seleccionar carpeta local en la app. 2. Descubrir campos (sube un PDF de muestra por tipo). 3. Ejecutar extraccion (crea tarea `source=desktop` e ingesta cada archivo). 4. Consultar tareas y exportar CSV opcional. #### Modo legacy API / curl Si llamas al API con `root_path` y rutas bajo `/data`, monta documentos al levantar Docker: ```bash IPS_DOCUMENT_ROOT=C:/ruta/a/documentos docker compose up --build ``` ### Error: No se encontraron documentos en /data Ese mensaje aparece en clientes legacy que envian `root_path=/data` sin montar documentos. La app desktop no deberia usar ese modo. Verifica: ```bash docker exec ipsextractor-extraction ls -la /data ``` Para la app desktop, asegurate de usar Electron (`npm run dev`) y de que el gateway responda en `http://localhost:8000`. ### Contrato De Base De Datos Los servicios se integran por Postgres: - `extraction_batches` - `extraction_rows` - `tasks` - `cleaning_jobs` - `cleaned_rows` Cada servicio mantiene su propia copia del modelo SQLAlchemy para no depender de un paquete comun.