feat: Add database migration service with Alembic for PostgreSQL, integrating job management and counting functionalities. Update Docker Compose to include db-migrations service, enhancing the microservices architecture. Revise documentation for clarity on database schema and migration processes.

This commit is contained in:
erik
2026-07-06 00:50:50 -05:00
parent d95f8f6ad8
commit 081da999d4
43 changed files with 2911 additions and 2271 deletions
+86 -248
View File
@@ -1,276 +1,114 @@
## IPSExtractor Microservices
# IPSExtractor
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.
Sistema de extracción y análisis de datos desde facturas médicas PDF.
Arquitectura de microservicios: cada servicio tiene su propia API, dependencias, Dockerfile y código de dominio. 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
## Servicios
api_gateway/ API central opcional
api/ Proxy HTTP hacia extraction y cleaning
requirements.txt
Dockerfile
| Servicio | Puerto | Tecnología | Descripción |
|----------|--------|------------|-------------|
| `api_gateway` | 8000 | Python/FastAPI | Puerta de enlace única; enruta hacia los demás servicios |
| `extraction` | 8001 | Python/FastAPI | Extracción LLM de campos; orquestación de jobs y conteo |
| `cleaning` | 8002 | Python/FastAPI | Normalización y limpieza de datos extraídos |
| `count-api` | 8080 (→ 8081) | Go | Conteo de servicios en facturas con OCR + ONNX (sin estado) |
| `database` | 5432 | PostgreSQL 16 | Base de datos compartida |
| `redis` | 6379 | Redis 7 | Cola ARQ para workers de extracción |
| `desktop_app` | local | Electron + React | Aplicación de escritorio |
desktop_app/ App desktop Windows/Mac
electron/ Main process, preload y dialogos nativos
src/ React UI, cliente API y export CSV
package.json
---
## Flujo principal
```
Desktop app
│
├─ Conteo (OCR + ML)
│ ├─ POST /count/v1/count → count-api (lotes de 10 PDFs)
│ ├─ POST /count-jobs → extraction (guarda en PostgreSQL)
│ └─ PATCH /count-jobs → extraction (validación humana)
│
└─ Extracción (LLM)
├─ POST /tasks/extraction/from-count → extraction (lee desde /data)
├─ POST /tasks/{id}/ingest-many → extraction (subida de PDFs)
└─ GET /tasks/{id}/rows → extraction (resultados)
```
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
## Arrancar el stack completo
```bash
# Clonar y configurar
cp .env.example .env
# Editar .env con las API keys del LLM y la ruta de documentos
docker compose up --build
```
Health checks:
```bash
curl http://localhost:8000/health
curl http://localhost:8001/health
curl http://localhost:8002/health
curl http://localhost:8000/health # gateway (agrega estado de los 3 servicios)
curl http://localhost:8081/health # count-api (directo)
```
### Flujo Directo Por Servicio
---
1. Ver modelos disponibles en extraccion:
## Variables de entorno principales
```bash
curl http://localhost:8001/models
| Variable | Descripción |
|----------|-------------|
| `IPS_DOCUMENT_ROOT` | Carpeta local con los documentos PDF (montada en `/data`) |
| `EXTRACTOR_API_KEY` | API key del LLM (OpenAI, xAI, etc.) |
| `EXTRACTOR_BASE_URL` | URL base del LLM (default: OpenAI) |
| `EXTRACTOR_MODEL` | Modelo de extracción (default: `gpt-5.4-mini`) |
| `WORKER_REPLICAS` | Réplicas del worker de extracción (default: `2`) |
Ver `.env.example` para la lista completa.
---
## Estructura del repositorio
```
services/
api_gateway/ — proxy HTTP (Python) → README
extraction/ — LLM + jobs + conteo BD (Python) → README
cleaning/ — limpieza de datos (Python) → README
count-api/ — OCR + ONNX stateless (Go) → README
db/ — migraciones Alembic → README
desktop_app/ — app Electron + React → README
docs/
database.dbml — esquema completo de la BD
docker-compose.yml — stack completo
.env.example — variables de entorno de referencia
```
2. Descubrir campos:
---
```bash
curl -X POST http://localhost:8001/schemas \
-H "Content-Type: application/json" \
-d '{"document_path": "/data/radicado-1/factura.pdf"}'
## Base de datos (esquema resumido)
```
jobs — contenedor de un trabajo del usuario
tasks — módulo ejecutado (counting / extraction / cleaning)
count_job_files — resultado de conteo por PDF
extraction_batches — lote de extracción
extraction_rows — fila extraída por documento
cleaning_jobs — job de limpieza
cleaned_rows — fila transformada
```
3. Ejecutar extraccion:
Esquema completo: [`docs/database.dbml`](docs/database.dbml)
```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`.
## Documentación por servicio
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.
- [`services/api_gateway/README.md`](services/api_gateway/README.md)
- [`services/extraction/README.md`](services/extraction/README.md)
- [`services/cleaning/README.md`](services/cleaning/README.md)
- [`services/services-count-facturas-medicas-serving/README.md`](services/services-count-facturas-medicas-serving/README.md)
- [`services/db/README.md`](services/db/README.md)
- [`services/desktop_app/README.md`](services/desktop_app/README.md)