Skip to content
GitHub

ADR-003: ETL Architecture

Aceptada - 2024 (ETL service implementado en Python)


El sistema Nostromo requiere ingesta automática de datos desde SII (Servicio de Impuestos Internos de Chile):

  • Datos necesarios: Indicadores económicos (UF, UTM, IPC), tablas paramétricas (tramos impuestos, tasas previsionales)
  • Fuente: Páginas web SII, PDFs publicados, APIs limitadas
  • Frecuencia: Mensual/trimestral para parámetros, diario para indicadores
  • Formato destino: PostgreSQL (schema parametros)
  • Web scraping: SII publica datos en HTML tables, PDFs, no tiene API completa
  • Parsing complejo: Tablas con formato irregular, colspan/rowspan, footnotes
  • Transformación: Datos crudos → formato normalizado para base de datos
  • Manejo de errores: SII cambia estructura de páginas sin previo aviso
  • Scheduling: ETL debe correr automáticamente según calendario

Implementar servicio ETL separado en Python, NO integrado en Orchestrator:

[SII Websites/PDFs]
[Python ETL Service]
- BeautifulSoup (HTML parsing)
- Pandas (data transformation)
- Schedule (cron-like)
[PostgreSQL - schema: parametros]
[Orchestrator - CommonDataService]
[Sevastopol Frontend]

Python 3.11+:

  • BeautifulSoup4: HTML parsing
  • Requests: HTTP client
  • Pandas: Data transformation
  • psycopg2: PostgreSQL driver
  • Schedule: Task scheduling

Deployment:

  • Container separado (Docker)
  • Cron jobs diarios (indicadores) y mensuales (parámetros)

ETL Service:

  • Scrape SII websites
  • Parse tables/PDFs
  • Transform to normalized format
  • Insert/update PostgreSQL

Orchestrator (CommonDataService):

  • READ-ONLY access a datos ETL
  • Expone datos vía API endpoints
  • NO modifica datos de parámetros

Python ecosystem para ETL

  • BeautifulSoup: Mejor HTML parser que alternativas Node.js
  • Pandas: Transformación de datos tabular (mejor que DataFrames JS)
  • Ecosystem maduro: Librerías para PDF parsing, OCR si se requiere

Separation of concerns

  • ETL no afecta latency de Orchestrator (requests HTTP)
  • Si ETL crashea, Orchestrator sigue funcionando (usa datos cached)
  • Deploy independiente: update ETL sin restart Orchestrator

Scheduling simplificado

  • Cron jobs en container ETL
  • No contamina codebase Orchestrator con logic ETL
  • Fácil debuggear logs ETL aislados

Error handling especializado

  • ETL puede retry múltiples veces sin afectar users
  • Alertas específicas si scraping falla (SII cambió estructura)
  • No afecta SLA de Orchestrator API

Performance

  • ETL corre offline (no en critical path de requests)
  • Orchestrator solo lee datos pre-procesados (queries rápidas)

Servicio adicional que mantener

  • Otro container, otro codebase, otro lenguaje
  • Deploy más complejo (Python deps, virtual env)
  • Mitigación: Containerización simplifica deploy

Comunicación inter-servicio

  • ETL escribe a PostgreSQL, Orchestrator lee
  • Si ETL falla silenciosamente, datos quedan desactualizados
  • Mitigación: Monitoring de timestamp última actualización, alertas si datos > 7 días

Duplicación de lógica DB

  • ETL tiene conexión PostgreSQL propia
  • Orchestrator también tiene pool PostgreSQL
  • Mitigación: Acceptable trade-off, cada servicio tiene propósito diferente

Barrera de entrada para equipo

  • Developers deben conocer Python + Node.js
  • Onboarding más complejo
  • Mitigación: ETL es simple, ~500-1000 líneas, fácil de aprender

No real-time

  • Datos actualizados en batch (diario/mensual)
  • Si SII publica UF nueva, puede tardar 24h en reflejarse
  • Mitigación: Para caso de uso actual (nóminas mensuales), delay es acceptable

Opción A: ETL integrado en Orchestrator (Node.js)

Section titled “Opción A: ETL integrado en Orchestrator (Node.js)”

Descripción: Escribir lógica ETL en TypeScript, correr en Orchestrator.

Rechazada porque:

  • Ecosystem Node.js pobre para scraping: Cheerio es limitado vs BeautifulSoup
  • Performance: Node.js single-threaded, scraping bloquearía event loop
  • Pandas no existe en JS: Alternativas (danfojs, Data-Forge) son inmaduras
  • Contamina codebase: Orchestrator es API backend, no ETL tool

Cuándo sería válida: Si datos vienen de API REST estructurada (no scraping), o volumen es muy pequeño (1-2 endpoints).


Opción B: Serverless ETL (AWS Lambda / Cloud Functions)

Section titled “Opción B: Serverless ETL (AWS Lambda / Cloud Functions)”

Descripción: ETL como funciones serverless en cloud.

Rechazada porque:

  • Cold start: Lambdas tienen ~1-3s cold start, inaceptable para scraping time-sensitive
  • Timeout limits: AWS Lambda max 15 min, algunos ETL jobs pueden ser más largos
  • Costo: Facturación por invocación, para jobs diarios puede ser caro
  • Vendor lock-in: Código específico a AWS/GCP

Cuándo sería válida: Si infraestructura es 100% cloud, o ETL corre muy esporádicamente (una vez al mes).


Descripción: Empleados actualizan parámetros manualmente en admin panel.

Rechazada porque:

  • Error-prone: Humanos cometen errores al copiar tablas
  • No escalable: Cada mes, empleado debe re-ingresar 100+ parámetros
  • Delay: Depende de disponibilidad humana, puede tardar días
  • No viable para startup lean

Cuándo sería válida: Si parámetros cambian raramente (una vez al año), o hay equipo grande de data entry.


Opción D: Usar API de terceros (ej: InvestChile API)

Section titled “Opción D: Usar API de terceros (ej: InvestChile API)”

Descripción: Pagar servicio que ya expone datos SII vía API REST.

Considerada pero rechazada:

  • Costo recurrente: APIs de terceros cobran mensualmente
  • Dependencia externa: Si servicio cae, Nostromo no funciona
  • No existe API completa: APIs existentes no tienen TODOS los parámetros que Nostromo necesita
  • SII data es pública: No tiene sentido pagar por datos que son gratuitos

Cuándo sería válida: Si presupuesto permite, y API cubre 100% de datos requeridos.


Implementación:

Decisiones relacionadas:

Referencias externas:


Indicadores diarios:

  • UF (Unidad de Fomento)
  • UTM (Unidad Tributaria Mensual)
  • IPC (Índice de Precios al Consumidor)

Parámetros mensuales/anuales:

  • Tramos impuestos (escalas PPM, global complementario)
  • Tasas previsionales (AFP, Isapre, Fonasa)
  • Topes imponibles
  • Sueldo mínimo

Cron schedule:

# Indicadores diarios (UF, UTM) - Run at 8 AM
0 8 * * * python etl/daily_indicators.py
# Parámetros mensuales - Run 1st of month
0 9 1 * * python etl/monthly_params.py
# Parámetros anuales - Run Jan 15
0 10 15 1 * python etl/annual_params.py

Estrategias:

  1. Retry con backoff: Si scraping falla, retry 3 veces con delay exponencial
  2. Alertas: Slack/email si ETL falla después de retries
  3. Fallback: Si datos nuevos no se obtienen, usar últimos datos disponibles
  4. Manual override: Admin panel permite upload manual de parámetros si ETL falla

Métricas:

  • Timestamp última actualización (por tipo de dato)
  • Success rate de ETL jobs (%)
  • Latency de scraping (tiempo de ejecución)

Alertas:

  • Si last_updated > 7 days → CRITICAL
  • Si ETL job falla 3 veces consecutivas → HIGH
  1. SII cambia estructura sin aviso: ETL debe ser resiliente a cambios HTML

    • Solución: Tests unitarios con HTML samples, fail gracefully si estructura cambia
  2. PDFs son complicados: Algunos parámetros solo vienen en PDF

    • Solución: Usar tabula-py (PDF table extractor), fallback a manual entry si falla
  3. Rate limiting: SII puede bloquear si scrapeamos muy rápido

    • Solución: Delays entre requests (2-3s), user-agent headers

Mejoras planeadas:

  1. Cacheo de HTML: Guardar HTML scrapeado para debugging posterior
  2. Diff detection: Alertar si datos cambian significativamente (ej: UF sube 50% → probablemente error)
  3. API SII oficial: Si SII lanza API, migrar de scraping a API calls