NutriFlow — Data Quality Framework

Pipeline de validación · Great Expectations + dbt + Data Contracts · Reporte diario de calidad
RUN #2847
Ejecutado: 2026-06-16 06:02:14 UTC
Entorno: production
Origen: BigQuery · NUTRIFLOW.RAW
PIPELINE DETENIDO — 1 de 6 tablas ha fallado las validaciones. Job de dbt cancelado. Notificación enviada a #data-alerts (Slack) y data-team@nutriflow.io.
Tablas validadas
6
3 fuente + 3 mart
Tablas OK
5
83% pass rate
Tablas fallidas
1
raw_mediciones
Expectations
47
total ejecutadas
Fallidas
3
6.4% failure rate
Warnings
2
no bloquean
Flujo del Pipeline
📥
App móvil
Ingesta raw
🔍
GE Source
Validation
3 suites
BLOQUEADO
raw_mediciones
3 checks fail
🔄
dbt run
transformaciones
🧪
dbt tests
marts
Marts OK
5/5 pass
📊
Reporting
layer
BI / API
Resultados por Tabla
Tabla Tipo Criticidad Suite / Tests Estado Pass rate Checks OK/Total Filas validadas Duración
raw_pacientes raw alta pacientes_suite · GE ● PASS 100% 9 / 9 218 1.2s
raw_consultas raw crítica consultas_suite · GE ● PASS 100% 11 / 11 1.487 2.1s
raw_mediciones raw media mediciones_suite · GE ● FAIL 79% 11 / 14 4.821 3.8s
dim_pacientes mart alta dbt tests · schema ● PASS 100% 6 / 6 85.432 4.5s
fct_consultas mart crítica dbt tests + recency ● PASS 100% 8 / 8 1.487 5.2s
fct_mediciones mart media dbt tests · schema ● WARN 90% 9 / 10 2.104.389 18.4s
Checks Fallidos — raw_mediciones
expect_column_values_to_be_between(peso_kg)
Rango esperado: 2 ≤ x ≤ 300
Observado: 37 registros fuera de rango (min=-4.2, max=842.0)
Filas afectadas: 0.77% (37/4.821)
expect_column_values_to_be_between(talla_cm)
Rango esperado: 40 ≤ x ≤ 250
Observado: 12 registros con talla_cm = 0.0
Filas afectadas: 0.25% (12/4.821)
expect_column_values_to_not_be_null(imc_calculado)
Esperado: 0 nulos
Observado: 49 nulos encontrados (1.02%)
Causa probable: bug en app v2.4.1 (columna derivada no persistida)
Acción tomada: Pipeline detenido. Datos de raw_mediciones NO propagados a fct_mediciones.
Ticket creado: INFRA-2041 — Validar bug app v2.4.1 sensor export
Warnings — fct_mediciones
!
expect_column_mean_to_be_between(imc)
Rango dinámico esperado: 22.5 – 27.5
Observado: media = 27.8 (ligera desviación)
No bloquea. Monitoreando tendencia.
Great Expectations Suite — raw_mediciones
mediciones_suite.py
# expectations/mediciones_suite.py import great_expectations as gx from great_expectations.core import ExpectationSuite from great_expectations.core.expectation_configuration import ( ExpectationConfiguration ) def build_mediciones_suite() -> ExpectationSuite: suite = ExpectationSuite("mediciones_suite") # --- COMPLETITUD --- for col in ["medicion_id", "paciente_id", "fecha_medicion", "imc_calculado"]: suite.add_expectation(ExpectationConfiguration( expectation_type="expect_column_values_to_not_be_null", kwargs={"column": col} )) # --- VALIDEZ PESO --- suite.add_expectation(ExpectationConfiguration( expectation_type="expect_column_values_to_be_between", kwargs={ "column": "peso_kg", "min_value": 2, # bebés "max_value": 300, # caso extremo "strict_min": True } )) # --- VALIDEZ TALLA --- suite.add_expectation(ExpectationConfiguration( expectation_type="expect_column_values_to_be_between", kwargs={ "column": "talla_cm", "min_value": 40, "max_value": 250 } )) # --- IMC ESTADÍSTICO --- suite.add_expectation(ExpectationConfiguration( expectation_type="expect_column_mean_to_be_between", kwargs={ "column": "imc_calculado", "min_value": 18.5, "max_value": 35.0, "result_format": "SUMMARY" } )) # --- FRESCURA --- suite.add_expectation(ExpectationConfiguration( expectation_type="expect_column_max_to_be_between", kwargs={ "column": "fecha_medicion", "min_value": {"$PARAMETER": "now - timedelta(days=1)"}, "max_value": {"$PARAMETER": "now"} } )) return suite
dbt Tests — Marts
_core__models.yml
test_validez_consulta.sql
version: 2 models: - name: fct_consultas description: Tabla de hechos de consultas nutricionales tests: - dbt_utils.recency: # frescura < 26h datepart: hour field: fecha_inicio_consulta interval: 26 - dbt_utils.at_least_one - dbt_utils.expression_is_true: # consistencia fechas expression: "fecha_fin_consulta >= fecha_inicio_consulta" columns: - name: consulta_id tests: - unique - not_null - name: paciente_id tests: - not_null - relationships: to: ref('dim_pacientes') field: paciente_id - name: estado_consulta tests: - accepted_values: values: ["programada", "en_curso", "completada", "cancelada", "no_presentado"] - name: duracion_minutos tests: - not_null - dbt_utils.expression_is_true: expression: ">= 5 AND duracion_minutos <= 180" - name: dim_pacientes columns: - name: paciente_id tests: - unique - not_null - name: email tests: - unique - not_null - dbt_utils.expression_is_true: expression: "email ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$'"
Contrato de Datos — raw_consultas v1.2.0
Contrato formal entre Backend Team (productor) y Data Platform Team (consumidor). Firmado 2026-05-10. SLA: disponibilidad 99.9%, frescura < 1h. Próxima revisión: 2026-08-10.

Schema Fields

consulta_id uuid REQUIRED
paciente_id uuid PII·indirecto
dietista_id uuid REQUIRED
fecha_inicio_consulta datetime REQUIRED
fecha_fin_consulta datetime nullable
estado_consulta enum REQUIRED
notas_clinicas text PII·directo
duracion_minutos integer 5–180

Quality SodaCL

checks for raw_consultas:
  - row_count > 0
  - missing_count(consulta_id) = 0
  - duplicate_count(consulta_id) = 0
  - missing_count(paciente_id) = 0
  - invalid_count(estado_consulta) = 0:
      valid values: [programada,
        en_curso, completada,
        cancelada, no_presentado]
  - freshness(fecha_inicio_consulta) < 1h
  - duplicate_count(consulta_id) = 0

SLA Actual

Disponibilidad 99.9% 99.97%
Frescura < 1h 23 min
Latencia ingesta < 5 min 3.2 min
Completitud 100% 100%
Unicidad ID 100% 100%
Versión contrato v1.2.0 vigente
Matriz de Cobertura por Dimensión de Calidad
Completitud
Unicidad
Validez
Consistencia
Frescura
Estadístico
raw_pacientes
raw_consultas
raw_mediciones
dim_pacientes
fct_consultas
fct_mediciones
Cubierto y pasado  ·  Cubierto con warning/fallo  ·  No aplica para esta tabla