VisaPredict AIVisaPredictAI
← VisaPredict AIIngeniería de datos

Cómo se construyó y entendió el panel: pipeline, análisis exploratorio, ingeniería de características, prácticas MLOps, estructura del repositorio y almacén en esquema estrella.

Construcción de la base de datos

El primer entregable ya está construido: el panel multiserie del U.S. Visa Bulletin —extraído, anotado, consolidado y auditado de extremo a extremo— desde el repositorio público VisaPredictAI. Esta sección documenta con detalle cómo se levantó y qué se halló en el camino.

194
Series país×categoría×tabla
27,611
Observaciones mensuales
296
Boletines · dic-2001 → 2026
21
Categorías canónicas
APTO
Veredicto mega-auditoría

01 El panel multiserie

Cada celda del panel es la fecha de prioridad de una combinación (país , categoría , tabla ) en el mes , transformada a días desde una fecha base ( 1 de enero de 1975, anterior a la prioridad más antigua observada: noviembre de 1979 en Filipinas F4). Cada observación conserva además su estado administrativoCurrent (C), Final (F, fecha específica) o Unavailable (U)— como anotación descriptiva. El objetivo predictivo se entrena exclusivamente sobre las observaciones con estado F.

5 países: MX · IN · CN · PH · All Charg. 16 categorías de empleo · 5 familiares 2 tablas: FAD & DFF (separadas) 15,931 obs entrenables (estado F · 58 %)
Empleo · FAD
11,548

desde dic-2001

Empleo · DFF
5,698

desde oct-2015

Familiar · FAD
7,115

desde dic-2001

Familiar · DFF
3,250

desde oct-2015

02 Arquitectura del pipeline de datos

De HTML crudo a panel auditado en seis etapas reproducibles, ejecutadas por una GitHub Action al publicarse un nuevo boletín.

1. Fuente Visa Bulletin HTML travel.state.gov 296 boletines dic-2001 → 2026 2. Scraper retry + backoff detección tolerante 5 familias de formato HTML 3. Anotación estado por celda C / F / U / UNK sólo F es objetivo 4. Normaliza 21 categorías canónicas días desde = 1975 5. Panel y(p,c,b,t) 194 series 27,611 obs CSV licencia abierta 6. Auditoría 12 dimensiones 0 críticos veredicto APTO Ejecutado al publicarse cada boletín · datos crudos generados sólo por script, nunca editados a mano.

03 Las cinco brechas resueltas

La base heredada cubría sólo cuatro categorías de empleo, una tabla y perdía la información de estado. Cada brecha se cerró con una corrección verificada (auditoría antes → después).

H1 · Estado

Anotación C/F/U preservada

El código aplanaba Current→fecha del boletín y Unavailable→vacío, borrando el régimen. Ahora una columna status lo conserva y el modelo entrena sólo sobre F.

antes: estado perdidoahora: C/F/U/UNK
H2 · Tabla DFF

Dates for Filing de empleo

El scraper cortaba tras la primera tabla (sólo FAD). Ahora captura las dos tablas employment-based etiquetadas por tipo; DFF disponible desde oct-2015.

antes: sólo FADahora: FAD + DFF
H3 · Categorías

EB-5 y subcategorías

El filtro descartaba todo salvo EB-1 a EB-4. Un clasificador normaliza 20 años de deriva de etiquetas a 16 códigos canónicos (EB-5, Other Workers, set-asides…).

antes: 4 categoríasahora: 16 + 5 familiares
H4 · Cobertura

Extensión al piso de la fuente

Detección robusta de columnas (categoría = col 0; país por nombre normalizado) recuperó 2001–2003 y reparó la serie residual (All Charg. estaba truncada a 2016).

antes: desde oct-2003ahora: desde dic-2001
H5 · UNK

Vacío desambiguado

El antiguo «vacío» mezclaba Unavailable con celdas no parseadas. El estado distingue ahora U de UNK (centinela elegido para no colisionar con la coerción a NaN de pandas): en el panel actual, una sola celda UNK.

antes: vacío ambiguoahora: U ≠ UNK
Bonus · Robustez

Sin pérdidas silenciosas

La auditoría detectó un mes (dic-2007) perdido por un fallo HTTP transitorio. Se añadió retry con backoff y reporte explícito: ningún mes se descarta en silencio.

antes: caída silenciosaahora: reintento + aviso

04 Taxonomía de categorías y la odisea del EB-5

Normalizar las categorías exigió absorber 68 variantes de etiqueta distintas (saltos de línea, espacios duros, errores tipográficos) acumuladas en dos décadas. El caso extremo es la quinta preferencia de empleo (EB-5), cuya estructura cambió de régimen tres veces: el sistema captura cada época bajo un código canónico estable.

Evolución de régimen de la categoría EB-5 5th · TEA · Pilot EB5 · EB5_TEA · EB5_PILOT TEA + Pilot EB5_TEA Regional / Non-Regional Center EB5_RC · EB5_NONRC Unreserved + 3 Set-Asides RURAL·HIGHUNEMP·INFRA 2003 2011 2015 2022 2026 16 códigos canónicos de empleo normalizados desde 68 variantes históricas; la desambiguación respeta que «regional center» es subcadena de «non-regional center» y de los rótulos TEA previos.

05 Búsqueda profunda de boletines faltantes

Se enumeró cada mes esperado y se sondeó tanto la URL construida como el Wayback Machine. La cobertura a nivel boletín es de 296 de 296 meses (100 %). Los cinco boletines retirados de travel.state.gov (marzo, septiembre, octubre y noviembre de 2009; octubre de 2012) se recuperaron manualmente del archivo histórico, se congelaron junto al resto de instantáneas y forman parte del panel.

296 / 296 meses presentes 5 boletines recuperados del archivo 0 huecos no explicados
🗓️ Alcance temporal honesto

Las Final Action Dates se publican desde 1992 [14], pero la serie mensual estructuralmente homogénea que el módulo consolida desde el HTML del boletín abarca de forma continua diciembre de 2001 – 2026 (296 meses completos por serie). El periodo 1992–2001 existe en fuentes estadísticas y de archivo, reservado a integración posterior. Las Dates for Filing sólo existen desde octubre de 2015 (130 observaciones).

06 Mega-auditoría de calidad (12 dimensiones)

El panel se somete a una auditoría exhaustiva: esquema, completitud, inventario de series, distribución de estado, huecos, unicidad de clave, validez de fechas, coherencia FAD vs DFF, anomalías de salto, reconciliación fuente↔panel, matriz de cobertura y entrenabilidad. Veredicto: APTO — cero hallazgos críticos, cero advertencias.

Distribución de estado administrativo

F · 15,931 · 58 % C · 10,896 · 40 % F = fecha específica (objetivo) · C = Current · U = Unavailable (568 · 2 %)

Dos hallazgos informativos (fenómenos reales, no errores)

Informativo · 1

6 inversiones DFF < FAD

El Departamento de Estado publica ocasionalmente fechas de presentación pocos días por detrás de la acción final. Los valores crudos parsean correctamente: son datos reales, no fallos de extracción.

Informativo · 2

14 saltos fuertes mes a mes

Retrogresiones y avances reales —la transición EB-4 de diciembre de 2022 y los rezagos de México F1/F3 de los años 80—. El sistema predictivo deberá tolerar las retrogresiones como fenómeno legítimo.

Prácticas MLOps

La base de datos no es un script suelto: es un sistema de ingeniería reproducible, probado y auto-protegido que se ejecuta de forma automatizada. Esta sección documenta cada práctica MLOps en producción en el repositorio VisaPredictAI.

139
Pruebas automatizadas
2
Workflows de CI
≥65 %
Piso de cobertura (gate CI)
ruff+mypy
Lint · formato · tipos
0
Críticos en auditoría

01 Reproducibilidad determinista

El entorno de desarrollo y el de integración continua son idénticos: mismas versiones de Python y de cada dependencia, fijadas con pin exacto. Cualquier persona reproduce el mismo dato byte por byte.

dependencias con pin exacto (`pandas==3.0.0`…) Python 3.14 · dev = CI `.python-version` + `requirements.txt` + `pyproject.toml` salida del pipeline verificada byte-idéntica

02 Pruebas automatizadas en cuatro niveles

La suite de pruebas del repositorio corre bajo pytest con un piso de cobertura ejecutable (`fail_under=65`): la cobertura no puede erosionarse sin romper el CI (el conteo exacto vive en el CI, no en esta prosa).

Nivel 1 · 12 casos

Funciones puras

Clasificadores de estado (C/F/U/UNK) y de categoría (20 años de deriva de etiquetas, desambiguación de subcadenas). Sin I/O, deterministas.

Nivel 2 · 10 casos

Extracción offline

El parseo real corre sobre 4 fixtures HTML guardados (eras de formato 2002/2007/2020/2022) sin red: rápido, determinista, cubre la lógica donde viven los bugs.

Nivel 3 · 12 invariantes

Contrato del panel

Invariantes duras del producto: clave única, 0 días negativos, dominio de estado, fecha de prioridad sólo en `F`, completitud de meses (un mes caído por la red falla el gate y dispara la alerta). Cualquier regresión bloquea el commit.

Nivel 4 · 31 casos

Contrato del almacén

El esquema estrella DuckDB se carga bajo constraints PK/FK/CHECK vivos: verifican que el panel se reconstruye sin pérdida (v_panel_long), que cada hecho respeta el contrato y que las vistas y marts cuadran.

03 Integración continua: dos pipelines

Un pipeline valida el código en cada cambio; otro refresca el dato cuando llega un boletín nuevo, con un gate de calidad antes de publicar.

CI de código · cada push / PR (ci.yml) push / PR a main ruff lint + formato mypy tipos pytest + cobertura suite pytest · gate ≥ 65 % Refresco de datos · por boletín (freeze_and_rebuild.yml) cron L-V 12:00 NY scrape offline · 3 secc. build panel gate de tests rojo = no publica commit + push en fallo → abre/comenta un issue de alerta

04 Robustez ante el mundo real

La fuente es HTML vivo de 20 años con formatos cambiantes y fallos transitorios. El pipeline está blindado contra cada modo de fallo observado.

Red

Retry + backoff

Un fallo HTTP transitorio (5xx, timeout) reintenta con espera creciente; un 4xx permanente ya no se reintenta. Ya no se pierde un mes en silencio (antes un `except: pass` los tragaba).

Reproducibilidad

Orden determinista

Las filas se ordenan por clave completa, no por un sort inestable: un mes caído ya no reordena en cascada el resto del archivo.

Integridad

Abort en fallo masivo

Si fallan >10 boletines (rediseño/caída de la fuente), el scraper aborta sin escribir: nunca se publica un panel degradado.

Observabilidad

Alertas en fallo

Un gate rojo o un crash abren (o comentan) un issue de seguimiento: el fallo avisa, no muere callado en la pestaña de Actions.

Correctitud

Centinela `UNK`

El estado «sin dato» usa `UNK`, no `NA`: el string `"NA"` lo coerciona pandas a NaN al leer y borraba la anotación. `UNK` es seguro para cualquier consumidor.

Detección

Hallado por los tests

El bug del centinela y un mes perdido por la red los cazaron las pruebas y el reporte de fallos, no un usuario: la red de seguridad funcionando.

05 Contrato de datos, linaje y versionado

Contrato & linaje

El dato se valida solo

  • Invariantes duras del panel como pruebas ejecutables (el esquema es el contrato).
  • Linaje a nivel celda: `raw_value` conserva la celda original tal cual se publicó.
  • Auditoría programática de 12 dimensiones con veredicto APTO (0 hallazgos críticos).
  • Hooks de pre-commit: bloqueo de archivos grandes, fin de línea, YAML/TOML, ruff.
Versionado

Cada artefacto donde corresponde

  • Los CSV abiertos viven en git: descargables directo, sin DVC ni credenciales (son el entregable).
  • Las figuras no se versionan (artefactos regenerables) para no inflar el historial.
  • DVC inicializado y reservado para los artefactos de modelo del próximo semestre (checkpoints, pronósticos).
  • `Makefile`: `make install · scrape · panel · test · check · all` (operación de un comando).

Estructura adaptada de Cookiecutter Data Science

El repositorio no organiza sus archivos al azar: sigue un contrato estructural propio adaptado de Cookiecutter Data Science (CCDS v2, una plantilla que fomenta justamente la adaptación), con desviaciones deliberadas y documentadas: dos paquetes con dirección de capas en lugar de un src/ único, cero notebooks (todo es script reproducible) y binarios de modelos por DVC, no en git. Quien conozca CCDS encuentra cada archivo donde espera —datos crudos separados de los derivados, reportes y documentación en su lugar, y un paquete instalable con una sola fuente de verdad para las dependencias— en el repositorio VisaPredictAI.

01 El árbol, de un vistazo

La convención CCDS separa por rol, no por tipo de archivo: el dato crudo (fuente inmutable) nunca se mezcla con el derivado (regenerable), y el código vive aparte de los reportes y la documentación.

VisaPredictAI/
├── data/
│   ├── raw/          # 10 CSV por país, recién scrapeados (fuente)
│   └── processed/    # visa_panel_long.csv — el panel derivado
├── reports/          # artefactos por rol: campaign · eval · prospective · governance
├── docs/             # documentación (DVC.md…)
├── tests/            # pytest + fixtures HTML offline
├── vp_data/          # paquete núcleo: config · visa_common · tracking
├── pipeline/         # DAG: freeze → scrape_* (empleo · familiar · DV) → build_panel → build_database → mega_audit
├── pyproject.toml    # deps + tooling + paquete (fuente única)
├── Makefile          # operación de un comando
├── LICENSE           # MIT
└── .github/workflows/ # CI de código + cron de datos

02 Qué hace cada pieza

data/raw/

Fuente inmutable

Los 10 CSV por país tal como salen de los scrapers. Nunca se editan a mano: si algo cambia, se corrige el scraper, no el dato.

data/processed/

Derivado regenerable

El panel multiserie que produce pipeline/build_panel.py. Separarlo del crudo deja claro qué es fuente y qué es resultado.

reports/ · docs/

Reportes y docs

Las auditorías de calidad (12 dimensiones) se escriben en reports/; la documentación operativa (p. ej. la estrategia DVC) vive en docs/.

tests/

Contrato ejecutable

Suite pytest con fixtures HTML para extracción offline e invariantes duras del panel: el esquema es el contrato.

vp_data/ · pipeline/

Núcleo compartido

El paquete vp_data/ centraliza rutas, constantes y helpers de scraping (fuente única); pipeline/ encadena los pasos del DAG de datos, cada uno invocable como python -m pipeline.<módulo>.

pyproject.toml

Una sola fuente de verdad

Dependencias (runtime y dev), configuración de ruff/mypy/pytest y el empaquetado, todo en un archivo: el paquete es instalable con pip install -e ..

03 Cómo se usa en el día a día

La estructura habilita un flujo de un solo comando y reproducible: el dato fluye de raw/ a processed/, y todo el toolchain se instala desde una sola fuente.

git clone https://github.com/UACJ-MIAAD/VisaPredictAI.git
cd VisaPredictAI
make install   # = pip install -e ".[dev]"  (runtime + herramientas)
make scrape    # escribe data/raw/*.csv
make panel     # consolida data/processed/visa_panel_long.csv
make check     # ruff + mypy + pytest (gate de cobertura)
make audit     # auditorías → reports/

04 Adaptaciones honestas

CCDS es un punto de partida, no un dogma. Se adopta lo que sirve a un proyecto de adquisición de datos y se omite, a conciencia, lo que aún no aplica —sin estructura de relleno.

licencia MIT en repo público paquete instalable + layout plano (`pip install -e .`) sin `notebooks/` ni `models/` todavía — el modelado es del próximo semestre (DVC ya reservado) sin `.env` — el scraping es de datos públicos, sin secretos `data/raw` y `data/processed` versionados en git (son el entregable abierto)

Almacén de datos en esquema estrella

El panel plano se eleva a un almacén dimensional normalizado en DuckDB: hechos centrales por grano, dimensiones conformes alrededor, y las invariantes del contrato promovidas a constraints declarativas (PK/FK/CHECK) que rechazan en la carga cualquier fila inválida. Cubre todas las categorías del Visa Bulletin —familia, empleo y Diversity Visa— con linaje, gobernanza y marts listos para el modelado. La definición vive en `schema.sql`; el modelo se reconstruye con `make db`.

7
Dimensiones
2
Hechos · fechas + rango DV
PK·FK·CHECK
El esquema es el contrato
139
Pruebas automatizadas
DuckDB
Embebido · SQL · Parquet
Diagrama entidad-relación del esquema estrella: dos hechos al centro, siete dimensiones, dimensiones conformes resaltadas, bridge de linaje, gobernanza y marts

Esquema estrella · hechos en dorado, dimensiones en azul, dimensiones conformes resaltadas, marts gold en verde, gobernanza en gris.

00 Por qué DuckDB: criterios de selección

La elección del motor responde al perfil de uso, no a la moda. El acceso a la base es analítico y de solo-lectura (escaneos, agregaciones y joins en estrella sobre ~27 000 filas), se regenera de un CSV versionado y debe correr en cualquier máquina al clonar: sin servidor, sin nube y sin costo. Sobre ese perfil se fijaron seis criterios.

Embebido

Cero servidor

Corre dentro del proceso (Python/CLI), sin demonio que instalar ni puerto que abrir. Clonar y make db basta: reproducibilidad total dev↔CI.

Analítico

Columnar y vectorizado

Motor OLAP orientado a columnas: los escaneos y agregaciones del panel son su caso de uso natural, no el transaccional fila a fila.

Local y gratis

Sin nube, sin cuenta

Decisión NO-AWS explícita: nada de credenciales, facturación ni red. El entregable académico se reproduce sin barreras.

SQL + contrato

PK/FK/CHECK nativas

SQL estándar con constraints declarativas que rechazan en la carga cualquier fila inválida: el esquema es el contrato de datos.

Interoperable

Parquet y pandas

Lee y escribe Parquet y DataFrames de forma nativa; el puente al modelado en Python es directo, sin un ETL intermedio.

Un archivo

Regenerable

La base es un solo archivo .duckdb derivado (gitignored); si se borra o corrompe, make db lo reconstruye en segundos.

Estado del arte: marco comparativo

En el panorama 2024–2025, DuckDB es el referente de base de datos analítica embebida (a menudo descrito como «el SQLite del OLAP»). Frente a las alternativas consideradas para este componente:

OpciónModeloServidorVeredictoRazón
DuckDBOLAP columnar, embebidoNoElegidoAnalítico, sin servidor, local y gratis, SQL con constraints, Parquet nativo
SQLiteOLTP por filas, embebidoNoParcialEmbebido y gratis, pero orientado a filas: lento en escaneos y agregaciones; sin columnar ni Parquet nativo
PostgreSQLOLTP por filas, cliente–servidorNoExcelente para apps concurrentes, pero exige un servidor corriendo — rompe el «clonar y correr»
Parquet + pandasArchivo + memoriaNoParcialRápido y columnar, pero sin motor SQL ni integridad relacional (sin PK/FK/CHECK = sin contrato)
BigQuery / Snowflake / RedshiftOLAP en la nubeGestionadoNoPotentes a gran escala, pero de pago, con cuenta y red — incompatibles con la reproducibilidad NO-AWS
🦆 Síntesis de la decisión

Para un panel analítico, pequeño y de solo-lectura que debe reproducirse gratis y sin servidor, DuckDB combina lo embebido de SQLite con el rendimiento columnar de un almacén de datos y las garantías de integridad de SQL. El Parquet tipado que exporta deja abierta la puerta a escalar a la nube en el futuro sin reescribir el modelo.

01 Catálogo de tablas, campos y llaves

El almacén tiene 12 tablas7 dimensiones, 2 hechos y 3 de gobernanza y procedencia — más 6 vistas/marts. Cada columna lleva su llave: PK primaria · FK foránea · UK única. Tipos entre paréntesis.

Dimensiones · 7

dimensión

dim_area

  • area_id PK (int)
  • slug UK · name (str)
  • is_residual_group (bool)
dimensión

dim_category

  • category_id PK
  • block · code UK(block,code)
  • parent_code · preference_level
  • is_subcategory · ina_basis
dimensión

dim_date

  • date_id PK
  • bulletin_date UK (date)
  • year · month · quarter
  • us_fiscal_year (int)
dimensión

dim_status

  • status PK (C/F/U/UNK)
  • label · description
  • is_predictable (bool · F)
dimensión

dim_table

  • table_id PK
  • code UK (FAD/DFF)
  • name (str)
dimensión

dim_region

  • region_id PK
  • slug UK · name
  • 6 regiones DV
dim · linaje

dim_category_alias

  • alias_id PK
  • category_id FK→dim_category
  • raw_label UK(cat,label)
  • valid_from·valid_to·n_months

Hechos · 2

hecho · fechas

fact_priority

  • PK compuesta: area_id·category_id·table_id·date_id (las 4 FK)
  • status FK→dim_status
  • priority_date (date · sólo F)
  • days_since_base = (int · sólo F) · raw_value
  • CHECK: dominio status · días ≥ 0 · definido sólo si F
hecho · rango DV

fact_dv_rank

  • PK compuesta: region_id·date_id (FK)
  • status FK→dim_status
  • rank_cutoff (int · sólo F)
  • raw_value · exceptions (por país)
  • CHECK: rango ≥ 0 · definido sólo si F

Gobernanza · 2

gobernanza

etl_run

  • run_id PK · built_at_utc (ts)
  • schema_version · n_fact_priority · n_fact_dv
  • n_trainable_f · pct_trainable (calidad)
  • panel_floor · panel_ceiling (date)
gobernanza

schema_version

  • version (int)
  • description (str)
  • detecta drift / evolución del esquema

Vistas y marts · 6: v_panel_long y v_dv_long reconstruyen el panel/DV sin pérdida; v_category_alias (linaje), v_trainable_by_preference (roll-up por preferencia), y los marts de modelado mart_training_F y mart_series_summary.

02 Todas las categorías, incluida la Diversity Visa

Familia (F1–F4) y empleo (EB-1 a EB-5 con 16 subcódigos canónicos) viven en el hecho de fechas fact_priority (). La Diversity Visa publica un número de rango por región, no una fecha — así que no cabe en el panel y obtiene su propio hecho fact_dv_rank (6 regiones × mes) y su dimensión dim_region.

familia + empleo + Diversity Visa DV = rango regional (hecho separado) Schedule A excluida con evidencia (no es categoría con fecha)

03 Jerarquía, dimensiones conformes y linaje

Jerarquía

Roll-ups por preferencia

dim_category lleva parent_code, preference_level y base estatutaria INA: las subcategorías se pliegan a su preferencia (todas las EB-5 bajo EB-5) vía la vista v_trainable_by_preference.

Conformes

Dimensiones compartidas

dim_date y dim_status son conformes: ambos hechos las comparten, así que el tiempo y el régimen C/F/U/UNK se definen una sola vez y se cruzan igual en todo el almacén.

Linaje

20 años de deriva, auditables

dim_category_alias saca la normalización de etiquetas del código a datos: cada grafía publicada → categoría canónica con su ventana de meses (48 alias; EB5_TEA tuvo 7 grafías 2001–2015).

04 Gobernanza, calidad y marts de modelado

Contrato

El esquema rechaza lo inválido

PK compuesta, FK a cada dimensión y CHECK (dominio de estado, días ≥ 0, variable definida sólo si status='F'): el contrato vive en el esquema, no sólo en los tests.

Provenance

Auditoría de carga + calidad

etl_run registra cada build (versión de esquema, conteos, score de entrenabilidad, piso/techo temporal); schema_version marca la evolución. Arquitectura medallón raw→panel→gold.

Marts gold

Listo para el modelado

mart_training_F = conjunto entrenable limpio (sólo F con la variable dependiente y features de tiempo); mart_series_summary resume cada serie para filtrar las evaluables.

05 Límites declarados (auditoría adversarial)

Una auditoría independiente re-derivó el modelo desde cero. Pasó fuerte —0 claves foráneas huérfanas, 48/48 alias re-clasifican a su canónico, las constraints rechazan toda fila inválida, contenido reproducible— y dejó estos límites sin barnizar:

Cobertura DV

2002–2003 parcial

El piso DV llega a 2001-12, pero ~20 meses de 2002–2003 publican la Diversity Visa en HTML no-tabular, aún no parseado (trabajo futuro). La advance notification (mes futuro) también queda fuera de alcance.

Sin degradación silenciosa

Gate de cobertura DV

Esos huecos ya no son silenciosos: test_dv_coverage_floor (en el gate de datos) exige pisos de filas, meses y meses completos, y aborta el commit si un scrape degrada la cobertura.