V4.0 — Auditoría Causal y Económica de Reclamo Comercial¶
Proyecto: Capstone Machine Learning aplicado a exportación de paltas Hass
Versión: V4.0
Tipo: Auditoría causal/económica sin entrenamiento de modelo
Target oficial: reclamo_comercial desde fact_inspeccion_destino
Unidad de auditoria: lote_exportacion_id / contenedor_id
Objetivo de esta versión¶
Antes de seguir entrenando modelos V4, esta auditoría busca responder:
- Qué variables podrían causar o explicar reclamos.
- Dónde ocurren los reclamos.
- Cuándo ocurren los reclamos.
- Con quién ocurren los reclamos.
- Cuánto cuesta actualmente el problema sin modelo predictivo.
- Qué variables deben entrar en futuros modelos
Esta versión no entrena modelos. Su foco es construir una base auditable para seleccionar variables y defender causalidad, asociación, riesgo y costo.
# ======================================================================================
# 0. INSTALACIÓN / IMPORTS / CONFIGURACIÓN GENERAL
# ======================================================================================
# Este notebook está diseñado para Google Colab + BigQuery.
# Si alguna librería no está disponible, se intenta instalar de forma controlada.
import sys
import os
import warnings
warnings.filterwarnings("ignore")
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pathlib import Path
try:
from google.colab import auth
IN_COLAB = True
except Exception:
IN_COLAB = False
try:
from google.cloud import bigquery
except Exception:
!pip -q install google-cloud-bigquery pyarrow db-dtypes
from google.cloud import bigquery
# Estilo gráfico sobrio y apto para presentación ejecutiva.
plt.rcParams["figure.figsize"] = (10, 5)
plt.rcParams["axes.grid"] = True
plt.rcParams["axes.spines.top"] = False
plt.rcParams["axes.spines.right"] = False
plt.rcParams["font.size"] = 10
RANDOM_STATE = 42
TARGET = "reclamo_comercial"
OUTPUT_DIR = Path("/content/capstone_outputs_V4_0_auditoria_causal_economica")
GRAF_DIR = OUTPUT_DIR / "graficos"
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
GRAF_DIR.mkdir(parents=True, exist_ok=True)
print("Output dir:", OUTPUT_DIR)
print("Random state:", RANDOM_STATE)
def print_step(title):
print("\n" + "="*100)
print(title)
print("="*100)
def savefig(name):
path = GRAF_DIR / name
plt.tight_layout()
plt.savefig(path, dpi=160, bbox_inches="tight")
print("[GRAFICO] Guardado:", path)
plt.show()
def safe_rate(num, den):
return np.where(np.asarray(den) == 0, 0, np.asarray(num) / np.asarray(den))
Output dir: /content/capstone_outputs_V4_0_auditoria_causal_economica Random state: 42
1. Autenticación BigQuery y lectura de tablas¶
La auditoría debe leer tres capas de información:
- Tablas operacionales: campo, clima, cosecha, packing/exportación e inspección destino.
- Tablas documentales de reclamos: facturas, notas de crédito, FF y detalle documental. Se usan para evidencia económica, no como predictores.
- Tablas HAB: parámetros, defectos, causas, protocolos, etapas y principios de gestión de calidad. Se usan como capa explicativa/causal.
# ======================================================================================
# 1. AUTENTICACIÓN Y CARGA ROBUSTA DE TABLAS BIGQUERY
# ======================================================================================
print_step("AUTENTICACIÓN Y CARGA DE DATOS BIGQUERY")
# En Colab, esto solicitará autorización de Google.
if IN_COLAB:
try:
auth.authenticate_user()
print("[OK] Autenticación Google completada.")
except Exception as e:
print("[WARNING] No se pudo autenticar automáticamente:", e)
# Ajustar si tu proyecto cambia.
PROJECT_ID = "capstone-ml-clasificacion"
DATASET_ID = "capstone_ml_ds"
LOCATION = "US"
client = bigquery.Client(project=PROJECT_ID, location=LOCATION)
# Diccionario completo esperado para V4.0.
# Si alguna tabla no existe, no se detiene la auditoría; se deja documentado.
TABLES = {
# 1) Tablas operacionales principales
"dim": "dim_cuarteles",
"clima": "fact_clima",
"cosecha": "fact_cosecha",
"expo": "fact_exportacion",
"insp": "fact_inspeccion_destino",
# 2) Tablas documentales reales de reclamos
"reclamo_documento_cabecera": "fact_reclamo_documento_cabecera",
"reclamo_factura_detalle": "fact_reclamo_factura_detalle",
"reclamo_nota_credito_detalle": "fact_reclamo_nota_credito_detalle",
"reclamo_ff_detalle": "fact_reclamo_ff_detalle",
# 3) Tablas HAB / calidad
"hab_parametro": "dim_hab_parametro_calidad",
"hab_defecto": "dim_hab_defecto_calidad",
"hab_causa_defecto": "bridge_hab_causa_defecto",
"hab_protocolo": "dim_hab_protocolo_operacional",
"hab_etapa": "dim_hab_etapa_cadena",
"hab_principio": "dim_hab_principio_gestion_calidad",
}
# Listar tablas reales del dataset para validar nombres y evitar errores 404 silenciosos.
try:
tables_real = [t.table_id for t in client.list_tables(f"{PROJECT_ID}.{DATASET_ID}")]
print("Tablas disponibles en BigQuery:")
for t in sorted(tables_real):
print(" -", t)
except Exception as e:
tables_real = []
print("[WARNING] No se pudo listar tablas del dataset:", e)
# Carga robusta: si una tabla no existe, se crea DataFrame vacío y continúa.
data = {}
load_audit = []
for alias, table_name in TABLES.items():
full_table = f"`{PROJECT_ID}.{DATASET_ID}.{table_name}`"
print(f"\nCargando {alias}: {table_name}...")
try:
df_tmp = client.query(f"SELECT * FROM {full_table}").to_dataframe()
data[alias] = df_tmp
print(f"[OK] {alias}: {df_tmp.shape}")
load_audit.append({"alias": alias, "tabla": table_name, "estado": "OK", "filas": df_tmp.shape[0], "columnas": df_tmp.shape[1], "error": ""})
except Exception as e:
data[alias] = pd.DataFrame()
print(f"[WARNING] No se pudo cargar {table_name}: {e}")
load_audit.append({"alias": alias, "tabla": table_name, "estado": "ERROR", "filas": 0, "columnas": 0, "error": str(e)[:300]})
load_audit_df = pd.DataFrame(load_audit)
display(load_audit_df)
load_audit_df.to_csv(OUTPUT_DIR / "auditoria_carga_tablas.csv", index=False)
# Variables cómodas para usar en el resto del notebook.
dim = data.get("dim", pd.DataFrame())
clima = data.get("clima", pd.DataFrame())
cosecha = data.get("cosecha", pd.DataFrame())
expo = data.get("expo", pd.DataFrame())
insp = data.get("insp", pd.DataFrame())
print("\nLectura metodológica:")
print("- Las tablas operacionales se usan para explicar origen, clima, cosecha, packing y logística.")
print("- La inspección destino entrega el target oficial reclamo_comercial.")
print("- Las tablas documentales y HAB se usan para auditoría, explicación y evidencia, no como predictores pre-despacho.")
==================================================================================================== AUTENTICACIÓN Y CARGA DE DATOS BIGQUERY ==================================================================================================== [OK] Autenticación Google completada. Tablas disponibles en BigQuery: - bridge_hab_causa_defecto - dim_cuarteles - dim_hab_defecto_calidad - dim_hab_etapa_cadena - dim_hab_parametro_calidad - dim_hab_principio_gestion_calidad - dim_hab_protocolo_operacional - dim_reclamo_caso_cliente - fact_clima - fact_cosecha - fact_exportacion - fact_inspeccion_destino - fact_reclamo_documento_cabecera - fact_reclamo_factura_detalle - fact_reclamo_ff_detalle - fact_reclamo_nota_credito_detalle Cargando dim: dim_cuarteles... [OK] dim: (144, 46) Cargando clima: fact_clima... [OK] clima: (14796, 26) Cargando cosecha: fact_cosecha... [OK] cosecha: (60480, 77) Cargando expo: fact_exportacion... [OK] expo: (58944, 90) Cargando insp: fact_inspeccion_destino... [OK] insp: (14736, 24) Cargando reclamo_documento_cabecera: fact_reclamo_documento_cabecera... [OK] reclamo_documento_cabecera: (9, 21) Cargando reclamo_factura_detalle: fact_reclamo_factura_detalle... [OK] reclamo_factura_detalle: (9, 12) Cargando reclamo_nota_credito_detalle: fact_reclamo_nota_credito_detalle... [OK] reclamo_nota_credito_detalle: (3, 15) Cargando reclamo_ff_detalle: fact_reclamo_ff_detalle... [OK] reclamo_ff_detalle: (114, 28) Cargando hab_parametro: dim_hab_parametro_calidad... [OK] hab_parametro: (18, 12) Cargando hab_defecto: dim_hab_defecto_calidad... [OK] hab_defecto: (12, 8) Cargando hab_causa_defecto: bridge_hab_causa_defecto... [OK] hab_causa_defecto: (22, 8) Cargando hab_protocolo: dim_hab_protocolo_operacional... [OK] hab_protocolo: (18, 9) Cargando hab_etapa: dim_hab_etapa_cadena... [OK] hab_etapa: (9, 8) Cargando hab_principio: dim_hab_principio_gestion_calidad... [OK] hab_principio: (8, 6)
| alias | tabla | estado | filas | columnas | error | |
|---|---|---|---|---|---|---|
| 0 | dim | dim_cuarteles | OK | 144 | 46 | |
| 1 | clima | fact_clima | OK | 14796 | 26 | |
| 2 | cosecha | fact_cosecha | OK | 60480 | 77 | |
| 3 | expo | fact_exportacion | OK | 58944 | 90 | |
| 4 | insp | fact_inspeccion_destino | OK | 14736 | 24 | |
| 5 | reclamo_documento_cabecera | fact_reclamo_documento_cabecera | OK | 9 | 21 | |
| 6 | reclamo_factura_detalle | fact_reclamo_factura_detalle | OK | 9 | 12 | |
| 7 | reclamo_nota_credito_detalle | fact_reclamo_nota_credito_detalle | OK | 3 | 15 | |
| 8 | reclamo_ff_detalle | fact_reclamo_ff_detalle | OK | 114 | 28 | |
| 9 | hab_parametro | dim_hab_parametro_calidad | OK | 18 | 12 | |
| 10 | hab_defecto | dim_hab_defecto_calidad | OK | 12 | 8 | |
| 11 | hab_causa_defecto | bridge_hab_causa_defecto | OK | 22 | 8 | |
| 12 | hab_protocolo | dim_hab_protocolo_operacional | OK | 18 | 9 | |
| 13 | hab_etapa | dim_hab_etapa_cadena | OK | 9 | 8 | |
| 14 | hab_principio | dim_hab_principio_gestion_calidad | OK | 8 | 6 |
Lectura metodológica: - Las tablas operacionales se usan para explicar origen, clima, cosecha, packing y logística. - La inspección destino entrega el target oficial reclamo_comercial. - Las tablas documentales y HAB se usan para auditoría, explicación y evidencia, no como predictores pre-despacho.
2. Auditoría general de tablas¶
Antes de unir datos, se revisa tamaño, columnas, nulos y duplicados por tabla. Esto permite justificar calidad de datos y detectar limitaciones del proyecto.
# ======================================================================================
# 2. AUDITORÍA GENERAL DE TABLAS
# ======================================================================================
print_step("AUDITORÍA GENERAL DE TABLAS")
table_summary = []
null_tables = []
for alias, df in data.items():
if df.empty:
table_summary.append({"alias": alias, "filas": 0, "columnas": 0, "duplicados": 0, "pct_nulos_promedio": np.nan})
continue
duplicated = int(df.duplicated().sum())
null_pct = df.isna().mean().mean()
table_summary.append({
"alias": alias,
"filas": df.shape[0],
"columnas": df.shape[1],
"duplicados": duplicated,
"pct_nulos_promedio": null_pct,
})
nulos = (
df.isna().sum()
.reset_index()
.rename(columns={"index": "variable", 0: "nulos"})
)
nulos["tabla"] = alias
nulos["pct_nulos"] = nulos["nulos"] / max(len(df), 1)
null_tables.append(nulos)
table_summary_df = pd.DataFrame(table_summary).sort_values("filas", ascending=False)
display(table_summary_df)
table_summary_df.to_csv(OUTPUT_DIR / "resumen_tablas_v40.csv", index=False)
if null_tables:
nulls_df = pd.concat(null_tables, ignore_index=True)
nulls_df.sort_values(["tabla", "pct_nulos"], ascending=[True, False]).to_csv(OUTPUT_DIR / "nulos_por_variable_v40.csv", index=False)
display(nulls_df.sort_values("pct_nulos", ascending=False).head(30))
# Gráfico de volumetría de tablas cargadas.
plot_df = table_summary_df[table_summary_df["filas"] > 0].copy().sort_values("filas")
plt.figure(figsize=(10, max(4, len(plot_df)*0.35)))
plt.barh(plot_df["alias"], plot_df["filas"])
plt.title("Volumetría de tablas cargadas")
plt.xlabel("Cantidad de registros")
plt.ylabel("Tabla")
savefig("01_volumetria_tablas.png")
print("Lectura:")
print("La auditoría de tablas permite demostrar qué fuentes están disponibles, cuáles faltan y qué tan robusta es la base para auditoria causal/económico.")
==================================================================================================== AUDITORÍA GENERAL DE TABLAS ====================================================================================================
| alias | filas | columnas | duplicados | pct_nulos_promedio | |
|---|---|---|---|---|---|
| 2 | cosecha | 60480 | 77 | 0 | 0.001759 |
| 3 | expo | 58944 | 90 | 0 | 0.001799 |
| 1 | clima | 14796 | 26 | 0 | 0.000000 |
| 4 | insp | 14736 | 24 | 0 | 0.002627 |
| 0 | dim | 144 | 46 | 0 | 0.000000 |
| 8 | reclamo_ff_detalle | 114 | 28 | 0 | 0.056391 |
| 11 | hab_causa_defecto | 22 | 8 | 0 | 0.039773 |
| 12 | hab_protocolo | 18 | 9 | 0 | 0.000000 |
| 9 | hab_parametro | 18 | 12 | 0 | 0.000000 |
| 10 | hab_defecto | 12 | 8 | 0 | 0.000000 |
| 5 | reclamo_documento_cabecera | 9 | 21 | 0 | 0.169312 |
| 6 | reclamo_factura_detalle | 9 | 12 | 0 | 0.000000 |
| 13 | hab_etapa | 9 | 8 | 0 | 0.000000 |
| 14 | hab_principio | 8 | 6 | 0 | 0.000000 |
| 7 | reclamo_nota_credito_detalle | 3 | 15 | 0 | 0.000000 |
| variable | nulos | tabla | pct_nulos | |
|---|---|---|---|---|
| 325 | unidad_medida_base | 114 | reclamo_ff_detalle | 1.000000 |
| 280 | puerto_o_referencia | 6 | reclamo_documento_cabecera | 0.666667 |
| 281 | documento_referenciado | 6 | reclamo_documento_cabecera | 0.666667 |
| 274 | codigo_postal | 5 | reclamo_documento_cabecera | 0.555556 |
| 278 | monto_neto | 3 | reclamo_documento_cabecera | 0.333333 |
| 279 | monto_total | 3 | reclamo_documento_cabecera | 0.333333 |
| 282 | ff_id_referenciado | 3 | reclamo_documento_cabecera | 0.333333 |
| 276 | forma_pago | 3 | reclamo_documento_cabecera | 0.333333 |
| 273 | direccion | 3 | reclamo_documento_cabecera | 0.333333 |
| 361 | parametro_id | 7 | hab_causa_defecto | 0.318182 |
| 261 | observacion_inspector | 929 | insp | 0.063043 |
| 142 | aceite_estimado_pct | 3669 | cosecha | 0.060665 |
| 236 | observacion_destino | 3575 | expo | 0.060651 |
| 235 | inspector_destino | 2945 | expo | 0.049963 |
| 114 | observacion_jefe_campo | 2442 | cosecha | 0.040377 |
| 141 | brix_pct | 2079 | cosecha | 0.034375 |
| 209 | fungicida_poscosecha | 1838 | expo | 0.031182 |
| 313 | ff_id | 3 | reclamo_ff_detalle | 0.026316 |
| 336 | numero_pallet | 3 | reclamo_ff_detalle | 0.026316 |
| 335 | kg_real | 3 | reclamo_ff_detalle | 0.026316 |
| 334 | codigo_productor | 3 | reclamo_ff_detalle | 0.026316 |
| 332 | productor_sap | 3 | reclamo_ff_detalle | 0.026316 |
| 331 | sap_lot | 3 | reclamo_ff_detalle | 0.026316 |
| 330 | grower | 3 | reclamo_ff_detalle | 0.026316 |
| 329 | moneda | 3 | reclamo_ff_detalle | 0.026316 |
| 328 | precio | 3 | reclamo_ff_detalle | 0.026316 |
| 327 | calibre_por_pallet | 3 | reclamo_ff_detalle | 0.026316 |
| 314 | entrega | 3 | reclamo_ff_detalle | 0.026316 |
| 323 | numero_boxes_lot | 3 | reclamo_ff_detalle | 0.026316 |
| 322 | product_packaging | 3 | reclamo_ff_detalle | 0.026316 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/01_volumetria_tablas.png
Lectura: La auditoría de tablas permite demostrar qué fuentes están disponibles, cuáles faltan y qué tan robusta es la base para auditoria causal/económico.
3. Construcción de base auditada a nivel lote_exportacion_id¶
La unidad modelable y auditable principal será el Packing List / lote exportación / contenedor.
El target oficial se construye desde fact_inspeccion_destino.reclamo_comercial y se agregará por lote_exportacion_id.
Importante: lote_llega_mal_destino y variables de inspección destino no se usan como predictoras pre-despacho. En esta auditoría pueden analizarse como evidencia post-arribo, pero no entran a modelos predictivos estrictamente pre-despacho.
# ======================================================================================
# 3. BASE AUDITADA A NIVEL LOTE_EXPORTACION / CONTENEDOR
# ======================================================================================
print_step("CONSTRUCCIÓN DE BASE AUDITADA")
# Validar tablas mínimas.
required = {"expo": expo, "insp": insp}
for name, df in required.items():
if df.empty:
raise ValueError(f"Tabla requerida vacía: {name}")
# Normalizar target oficial desde inspección destino.
insp2 = insp.copy()
insp2[TARGET] = pd.to_numeric(insp2.get(TARGET, 0), errors="coerce").fillna(0).astype(int)
if "monto_reclamo_usd" in insp2.columns:
insp2["monto_reclamo_usd"] = pd.to_numeric(insp2["monto_reclamo_usd"], errors="coerce").fillna(0)
else:
insp2["monto_reclamo_usd"] = 0.0
# Agregación oficial por lote_exportacion_id.
target_agg = (
insp2
.groupby("lote_exportacion_id", as_index=False)
.agg(
reclamo_comercial=(TARGET, "max"),
monto_reclamo_usd=("monto_reclamo_usd", "sum"),
n_inspecciones=("lote_exportacion_id", "size")
)
)
print("Distribución target oficial desde inspección destino:")
display(target_agg["reclamo_comercial"].value_counts().sort_index())
# Exportación: una fila por lote_exportacion_id, para evitar duplicidad por packing/lineas.
expo_base = expo.copy()
if "lote_exportacion_id" not in expo_base.columns:
raise ValueError("fact_exportacion no contiene lote_exportacion_id")
# En caso de duplicados de lote exportación, tomar primera fila operacional y luego auditar duplicidad.
dup_expo = expo_base["lote_exportacion_id"].duplicated().sum()
print("Duplicados lote_exportacion_id en expo:", dup_expo)
expo_unique = expo_base.drop_duplicates(subset=["lote_exportacion_id"]).copy()
# Eliminar targets si vinieran accidentalmente desde exportación.
leak_cols = [
"reclamo_comercial", "monto_reclamo_usd", "lote_llega_mal_destino",
"score_condicion_arribo_0a100", "fruta_apta_pct", "desorden_interno_pct",
"maduracion_desuniforme_pct", "deshidratacion_pct", "golpe_pct", "podredumbre_pct",
"dias_a_listo_consumo", "defecto_principal_destino", "severidad_inspeccion",
"fecha_inspeccion_destino", "inspector_empresa"
]
expo_unique = expo_unique.drop(columns=[c for c in leak_cols if c in expo_unique.columns], errors="ignore")
base = expo_unique.merge(target_agg, on="lote_exportacion_id", how="inner")
base[TARGET] = base[TARGET].fillna(0).astype(int)
print("Base auditada inicial:", base.shape)
print("Distribución target en base:")
display(base[TARGET].value_counts().sort_index())
# Validaciones esperadas conocidas del proyecto.
print("Tasa de reclamo:", f"{base[TARGET].mean():.2%}")
# Guardar base mínima auditada.
base.to_csv(OUTPUT_DIR / "base_auditada_lote_exportacion_v40.csv", index=False)
plt.figure(figsize=(6,4))
base[TARGET].value_counts().sort_index().plot(kind="bar")
plt.title("Distribución oficial del target reclamo_comercial")
plt.xlabel("reclamo_comercial")
plt.ylabel("Cantidad de lotes exportación")
savefig("02_distribucion_target_oficial.png")
print("Lectura:")
print("El target oficial proviene de fact_inspeccion_destino. La base auditada queda a nivel lote_exportacion_id y evita duplicar registros de exportación.")
==================================================================================================== CONSTRUCCIÓN DE BASE AUDITADA ==================================================================================================== Distribución target oficial desde inspección destino:
| count | |
|---|---|
| reclamo_comercial | |
| 0 | 14582 |
| 1 | 154 |
Duplicados lote_exportacion_id en expo: 0 Base auditada inicial: (14736, 81) Distribución target en base:
| count | |
|---|---|
| reclamo_comercial | |
| 0 | 14582 |
| 1 | 154 |
Tasa de reclamo: 1.05% [GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/02_distribucion_target_oficial.png
Lectura: El target oficial proviene de fact_inspeccion_destino. La base auditada queda a nivel lote_exportacion_id y evita duplicar registros de exportación.
4. Enriquecimiento operacional para auditoría¶
Se incorporan variables de origen, cosecha y clima cuando las llaves están disponibles. La auditoría usa estas variables para segmentar y analizar asociación, pero aún no entrena modelos.
# ======================================================================================
# 4. ENRIQUECIMIENTO OPERACIONAL: ORIGEN, COSECHA Y CLIMA
# ======================================================================================
print_step("ENRIQUECIMIENTO OPERACIONAL")
base_enriq = base.copy()
# Merge con cosecha por lote_cosecha_id si existe.
if not cosecha.empty and "lote_cosecha_id" in base_enriq.columns and "lote_cosecha_id" in cosecha.columns:
cosecha_cols_prefer = [
"lote_cosecha_id", "cuartel_id", "temporada", "fecha_muestreo", "fecha_cosecha",
"dias_muestreo_a_cosecha", "cuadrilla", "uso_tijera", "cantidad_bins", "horas_cosecha",
"estacion_climatica_ref", "subzona_agroclimatica", "comuna", "uniformidad_riego_pct",
"altitud_ms", "edad_arboles_anos", "densidad_arboles_ha", "portainjerto", "textura_suelo",
"ph_suelo", "mo_pct", "ce_suelo_ds_m", "sistema_riego"
]
cosecha_use = cosecha[[c for c in cosecha_cols_prefer if c in cosecha.columns]].drop_duplicates("lote_cosecha_id")
base_enriq = base_enriq.merge(cosecha_use, on="lote_cosecha_id", how="left", suffixes=("", "_cosecha"))
print("[OK] Merge cosecha:", base_enriq.shape)
else:
print("[INFO] No se realizó merge cosecha por falta de llave o tabla vacía.")
# Merge con dim por cuartel_id si existe.
if not dim.empty and "cuartel_id" in base_enriq.columns and "cuartel_id" in dim.columns:
dim_cols_prefer = [
"cuartel_id", "predio_id", "nombre_predio", "unidad_negocio", "comuna", "subzona_agroclimatica",
"latitud_aprox", "longitud_aprox", "altitud_ms", "superficie_ha", "ano_plantacion",
"edad_arboles_anos", "densidad_arboles_ha", "portainjerto", "textura_suelo",
"profundidad_suelo_cm", "ph_suelo", "mo_pct", "ce_suelo_ds_m", "sistema_riego",
"uniformidad_riego_pct", "indice_vigor_ndvi_base", "ndvi_base", "rendimiento_base"
]
dim_use = dim[[c for c in dim_cols_prefer if c in dim.columns]].drop_duplicates("cuartel_id")
base_enriq = base_enriq.merge(dim_use, on="cuartel_id", how="left", suffixes=("", "_dim"))
print("[OK] Merge dim_cuarteles:", base_enriq.shape)
else:
print("[INFO] No se realizó merge dim por falta de llave o tabla vacía.")
# Variables derivadas de fechas.
for col in ["fecha_cosecha", "fecha_embarque", "fecha_recepcion_packing"]:
if col in base_enriq.columns:
base_enriq[col] = pd.to_datetime(base_enriq[col], errors="coerce")
if "fecha_cosecha" in base_enriq.columns:
base_enriq["mes_cosecha"] = base_enriq["fecha_cosecha"].dt.month
base_enriq["semana_cosecha"] = base_enriq["fecha_cosecha"].dt.isocalendar().week.astype("float")
if "fecha_embarque" in base_enriq.columns:
base_enriq["mes_embarque"] = base_enriq["fecha_embarque"].dt.month
base_enriq["semana_embarque"] = base_enriq["fecha_embarque"].dt.isocalendar().week.astype("float")
# Agregación climática robusta por estación/temporada, si existe.
# Para V4.0 se calcula resumen por estación-temporada y se asigna por estacion_climatica_ref.
if not clima.empty and "estacion_climatica_ref" in clima.columns and "estacion_climatica_ref" in base_enriq.columns:
clima2 = clima.copy()
if "fecha" in clima2.columns:
clima2["fecha"] = pd.to_datetime(clima2["fecha"], errors="coerce")
numeric_clima_candidates = [
"tmin_c", "tmax_c", "tmed_c", "lluvia_mm", "humedad_relativa_pct",
"horas_sobre_30c", "evento_helada", "horas_frio_bajo_7c", "gdd_base10",
"radiacion_mj_m2", "temperatura_suelo_c", "viento_km_h", "eto_mm"
]
for c in numeric_clima_candidates:
if c in clima2.columns:
clima2[c] = pd.to_numeric(clima2[c], errors="coerce")
group_keys = ["estacion_climatica_ref"]
if "temporada" in clima2.columns:
group_keys.append("temporada")
agg_dict = {}
if "tmin_c" in clima2.columns: agg_dict["tmin_promedio_precosecha"] = ("tmin_c", "mean")
if "tmax_c" in clima2.columns: agg_dict["tmax_promedio_precosecha"] = ("tmax_c", "mean")
if "tmed_c" in clima2.columns: agg_dict["tmedia_promedio_precosecha"] = ("tmed_c", "mean")
if "lluvia_mm" in clima2.columns: agg_dict["lluvia_acumulada_precosecha"] = ("lluvia_mm", "sum")
if "humedad_relativa_pct" in clima2.columns: agg_dict["humedad_promedio_precosecha"] = ("humedad_relativa_pct", "mean")
if "horas_sobre_30c" in clima2.columns: agg_dict["horas_sobre_30c_acumuladas"] = ("horas_sobre_30c", "sum")
if "evento_helada" in clima2.columns: agg_dict["heladas_acumuladas_precosecha"] = ("evento_helada", "sum")
if "horas_frio_bajo_7c" in clima2.columns: agg_dict["horas_frio_acumuladas"] = ("horas_frio_bajo_7c", "sum")
if "gdd_base10" in clima2.columns: agg_dict["gdd_acumulado_precosecha"] = ("gdd_base10", "sum")
if "radiacion_mj_m2" in clima2.columns: agg_dict["radiacion_promedio_precosecha"] = ("radiacion_mj_m2", "mean")
if "temperatura_suelo_c" in clima2.columns: agg_dict["temperatura_suelo_promedio_precosecha"] = ("temperatura_suelo_c", "mean")
if agg_dict:
clima_agg = clima2.groupby(group_keys, as_index=False).agg(**agg_dict)
merge_keys = [k for k in group_keys if k in base_enriq.columns]
base_enriq = base_enriq.merge(clima_agg, on=merge_keys, how="left")
print("[OK] Merge clima agregado:", base_enriq.shape)
else:
print("[INFO] No se realizó merge clima por falta de estación o tabla vacía.")
# Deduplicar columnas exactas.
base_enriq = base_enriq.loc[:, ~base_enriq.columns.duplicated()].copy()
# Crear flags de riesgo si las variables existen.
def add_flag(df, col, new_col, condition):
if col in df.columns:
df[new_col] = condition(pd.to_numeric(df[col], errors="coerce")).astype(int)
return df
base_enriq = add_flag(base_enriq, "desviacion_materia_seca_pct", "riesgo_heterogeneidad_flag", lambda s: s >= s.quantile(0.75))
base_enriq = add_flag(base_enriq, "materia_seca_pct", "riesgo_materia_seca_baja_flag", lambda s: s <= s.quantile(0.25))
base_enriq = add_flag(base_enriq, "firmeza_pulpa_lb", "riesgo_firmeza_baja_flag", lambda s: s <= s.quantile(0.25))
base_enriq = add_flag(base_enriq, "dias_cosecha_a_packing", "packing_lento_flag", lambda s: s >= s.quantile(0.75))
base_enriq = add_flag(base_enriq, "transito_real_dias", "transito_largo_flag", lambda s: s >= s.quantile(0.75))
# Diferencia de tránsito si existen ambos campos.
if "transito_real_dias" in base_enriq.columns and "transito_plan_dias" in base_enriq.columns:
base_enriq["diferencia_transito_dias"] = pd.to_numeric(base_enriq["transito_real_dias"], errors="coerce") - pd.to_numeric(base_enriq["transito_plan_dias"], errors="coerce")
base_enriq["retraso_logistico_flag"] = (base_enriq["diferencia_transito_dias"] > 0).astype(int)
base_enriq.to_csv(OUTPUT_DIR / "base_enriquecida_auditoria_v40.csv", index=False)
print("Base enriquecida guardada:", base_enriq.shape)
print("Columnas disponibles:", len(base_enriq.columns))
==================================================================================================== ENRIQUECIMIENTO OPERACIONAL ==================================================================================================== [OK] Merge cosecha: (14736, 100) [OK] Merge dim_cuarteles: (14736, 121) [OK] Merge clima agregado: (14736, 136) Base enriquecida guardada: (14736, 140) Columnas disponibles: 140
5. Punto 1 — Auditoría Estratégica de Variables¶
Clasificamos variables en 5 niveles:
- Nivel 1 — Variables Causales Directas: 10 variables críticas, altamente defendibles por fisiología/calidad/logística.
- Nivel 2 — Variables Explicativas Primarias: 20 variables importantes de apoyo.
- Nivel 3 — Variables de Entorno Operacional: 20–30 variables contextuales.
- Nivel 4 — Variables de Riesgo Histórico y Comercial Acumulado: alrededor de 40 variables, incluyendo señales históricas sin leakage.
- Nivel 5 — Variables de Causalidad Expandida e Interacciones: sobre 50 variables, con interacciones e hipótesis causales ampliadas.
# ======================================================================================
# 5. AUDITORÍA ESTRATÉGICA DE VARIABLES — 5 NIVELES
# ======================================================================================
print_step("AUDITORÍA ESTRATÉGICA DE VARIABLES — 5 NIVELES")
# --------------------------------------------------------------------------------------
# Objetivo:
# Clasificar las variables candidatas del proyecto en 5 niveles de madurez analítica.
# Esto permite distinguir:
# - variables críticas fisiológicas/operacionales,
# - variables importantes,
# - variables contextuales,
# - variables históricas,
# - variables de causalidad expandida/interacciones.
#
# Además, se valida si cada variable existe realmente en las tablas cargadas.
# --------------------------------------------------------------------------------------
# Unir todas las columnas disponibles de las tablas cargadas
all_columns = set()
for table_name, df_tmp in data.items():
if isinstance(df_tmp, pd.DataFrame) and not df_tmp.empty:
for c in df_tmp.columns:
all_columns.add(c)
all_columns_lower = {c.lower(): c for c in all_columns}
def variable_exists(var_name):
"""
Valida si una variable existe en alguna tabla cargada.
La búsqueda es flexible:
- match exacto en minúscula
- match parcial para variables derivadas o nombres similares
"""
v = str(var_name).lower()
if v in all_columns_lower:
return True
for col in all_columns_lower:
if v in col or col in v:
return True
return False
variables_estrategicas = [
# ==================================================================================
# NIVEL 1 — VARIABLES CAUSALES DIRECTAS
# ==================================================================================
{
"nivel": "Nivel 1",
"nombre_nivel": "Variables Causales Directas",
"area": "Madurez",
"variable": "materia_seca_pct",
"justificacion": "Indicador maestro de madurez funcional de la palta Hass."
},
{
"nivel": "Nivel 1",
"nombre_nivel": "Variables Causales Directas",
"area": "Madurez",
"variable": "desviacion_materia_seca_pct",
"justificacion": "Captura heterogeneidad de madurez; puede generar maduración asincrónica."
},
{
"nivel": "Nivel 1",
"nombre_nivel": "Variables Causales Directas",
"area": "Madurez",
"variable": "riesgo_heterogeneidad_score",
"justificacion": "Score derivado para cuantificar riesgo de mezcla heterogénea."
},
{
"nivel": "Nivel 1",
"nombre_nivel": "Variables Causales Directas",
"area": "Frío",
"variable": "quiebre_cadena_frio_h",
"justificacion": "Mide exposición térmica fuera de rango; puede acelerar respiración y senescencia."
},
{
"nivel": "Nivel 1",
"nombre_nivel": "Variables Causales Directas",
"area": "Frío",
"variable": "tiempo_preenfriado_h",
"justificacion": "Demora en remover calor de campo aumenta riesgo de deterioro poscosecha."
},
{
"nivel": "Nivel 1",
"nombre_nivel": "Variables Causales Directas",
"area": "Frío",
"variable": "temperatura_setpoint_frio_c",
"justificacion": "Temperatura objetivo debe ser coherente con madurez y tiempo de tránsito."
},
{
"nivel": "Nivel 1",
"nombre_nivel": "Variables Causales Directas",
"area": "Logística",
"variable": "transito_real_dias",
"justificacion": "Mayor tránsito aumenta exposición a senescencia y riesgo de reclamo."
},
{
"nivel": "Nivel 1",
"nombre_nivel": "Variables Causales Directas",
"area": "Origen",
"variable": "indice_vigor_ndvi_base",
"justificacion": "Vigor vegetativo se asocia a condición fisiológica del fruto."
},
{
"nivel": "Nivel 1",
"nombre_nivel": "Variables Causales Directas",
"area": "Origen",
"variable": "subzona_agroclimatica",
"justificacion": "Define condiciones agroclimáticas y ventana de comercialización."
},
{
"nivel": "Nivel 1",
"nombre_nivel": "Variables Causales Directas",
"area": "Origen",
"variable": "uniformidad_riego_pct",
"justificacion": "Variabilidad de riego puede generar heterogeneidad de madurez y materia seca."
},
# ==================================================================================
# NIVEL 2 — VARIABLES EXPLICATIVAS PRIMARIAS
# ==================================================================================
{"nivel": "Nivel 2", "nombre_nivel": "Variables Explicativas Primarias", "area": "Origen", "variable": "edad_arboles_anos", "justificacion": "Edad del huerto puede influir en vigor, productividad y condición de fruta."},
{"nivel": "Nivel 2", "nombre_nivel": "Variables Explicativas Primarias", "area": "Origen", "variable": "densidad_arboles_ha", "justificacion": "Densidad afecta competencia, luz, vigor y desarrollo del fruto."},
{"nivel": "Nivel 2", "nombre_nivel": "Variables Explicativas Primarias", "area": "Origen", "variable": "portainjerto", "justificacion": "Portainjerto puede influir en vigor, absorción hídrica y respuesta al estrés."},
{"nivel": "Nivel 2", "nombre_nivel": "Variables Explicativas Primarias", "area": "Origen", "variable": "ph_suelo", "justificacion": "pH afecta disponibilidad nutricional y condición productiva."},
{"nivel": "Nivel 2", "nombre_nivel": "Variables Explicativas Primarias", "area": "Origen", "variable": "mo_pct", "justificacion": "Materia orgánica se relaciona con estructura del suelo y disponibilidad hídrica."},
{"nivel": "Nivel 2", "nombre_nivel": "Variables Explicativas Primarias", "area": "Clima", "variable": "tmin_c", "justificacion": "Temperatura mínima precosecha afecta metabolismo y desarrollo del fruto."},
{"nivel": "Nivel 2", "nombre_nivel": "Variables Explicativas Primarias", "area": "Clima", "variable": "tmax_c", "justificacion": "Temperatura máxima puede generar estrés térmico."},
{"nivel": "Nivel 2", "nombre_nivel": "Variables Explicativas Primarias", "area": "Clima", "variable": "lluvia_mm", "justificacion": "Lluvia precosecha puede afectar condición sanitaria y manejo de cosecha."},
{"nivel": "Nivel 2", "nombre_nivel": "Variables Explicativas Primarias", "area": "Clima", "variable": "horas_frio_bajo_7c", "justificacion": "Horas de frío pueden influir en fisiología y calendario productivo."},
{"nivel": "Nivel 2", "nombre_nivel": "Variables Explicativas Primarias", "area": "Clima", "variable": "horas_sobre_30c", "justificacion": "Horas sobre 30°C capturan estrés por calor."},
{"nivel": "Nivel 2", "nombre_nivel": "Variables Explicativas Primarias", "area": "Cosecha", "variable": "dias_muestreo_a_cosecha", "justificacion": "Ventana entre muestreo y cosecha afecta representatividad de madurez."},
{"nivel": "Nivel 2", "nombre_nivel": "Variables Explicativas Primarias", "area": "Cosecha", "variable": "semana_cosecha", "justificacion": "Captura momento fenológico y etapa de temporada."},
{"nivel": "Nivel 2", "nombre_nivel": "Variables Explicativas Primarias", "area": "Cosecha", "variable": "mes_cosecha", "justificacion": "Permite evaluar estacionalidad de cosecha."},
{"nivel": "Nivel 2", "nombre_nivel": "Variables Explicativas Primarias", "area": "Packing", "variable": "dias_cosecha_a_packing", "justificacion": "Tiempo desde cosecha a packing afecta condición y deterioro inicial."},
{"nivel": "Nivel 2", "nombre_nivel": "Variables Explicativas Primarias", "area": "Madurez", "variable": "firmeza_pulpa_lb", "justificacion": "Resistencia estructural del fruto frente a daño mecánico."},
{"nivel": "Nivel 2", "nombre_nivel": "Variables Explicativas Primarias", "area": "Madurez", "variable": "calibre", "justificacion": "Tamaño del fruto puede relacionarse con madurez y sensibilidad."},
{"nivel": "Nivel 2", "nombre_nivel": "Variables Explicativas Primarias", "area": "Madurez", "variable": "peso_fruto_g", "justificacion": "Peso captura condición física y calibre efectivo."},
{"nivel": "Nivel 2", "nombre_nivel": "Variables Explicativas Primarias", "area": "Logística", "variable": "retraso_logistico_flag", "justificacion": "Retrasos aumentan exposición a deterioro y riesgo comercial."},
{"nivel": "Nivel 2", "nombre_nivel": "Variables Explicativas Primarias", "area": "Logística", "variable": "diferencia_transito_dias", "justificacion": "Diferencia entre tránsito real y planificado."},
{"nivel": "Nivel 2", "nombre_nivel": "Variables Explicativas Primarias", "area": "Contenedor", "variable": "atmosfera_controlada", "justificacion": "Condición de atmósfera afecta conservación y maduración."},
# ==================================================================================
# NIVEL 3 — VARIABLES DE ENTORNO OPERACIONAL
# ==================================================================================
{"nivel": "Nivel 3", "nombre_nivel": "Variables de Entorno Operacional", "area": "Mercado", "variable": "pais_destino", "justificacion": "Destino puede reflejar distancia, exigencia comercial y manejo post-arribo."},
{"nivel": "Nivel 3", "nombre_nivel": "Variables de Entorno Operacional", "area": "Mercado", "variable": "macro_mercado", "justificacion": "Agrupa destinos con comportamiento logístico y comercial común."},
{"nivel": "Nivel 3", "nombre_nivel": "Variables de Entorno Operacional", "area": "Mercado", "variable": "canal_destino", "justificacion": "Canal puede reflejar exigencia de cliente y nivel de tolerancia."},
{"nivel": "Nivel 3", "nombre_nivel": "Variables de Entorno Operacional", "area": "Cliente", "variable": "cliente_tipo", "justificacion": "Tipo de cliente puede tener diferente estándar de aceptación."},
{"nivel": "Nivel 3", "nombre_nivel": "Variables de Entorno Operacional", "area": "Packing", "variable": "packhouse_id", "justificacion": "Captura diferencias operacionales entre plantas o instalaciones."},
{"nivel": "Nivel 3", "nombre_nivel": "Variables de Entorno Operacional", "area": "Packing", "variable": "linea_packing", "justificacion": "Diferencias de línea pueden afectar manipulación y selección."},
{"nivel": "Nivel 3", "nombre_nivel": "Variables de Entorno Operacional", "area": "Packing", "variable": "operador_packing", "justificacion": "Operador puede reflejar diferencias operativas o turnos."},
{"nivel": "Nivel 3", "nombre_nivel": "Variables de Entorno Operacional", "area": "Logística", "variable": "naviera", "justificacion": "Naviera captura diferencias de servicio, ruta y desempeño logístico."},
{"nivel": "Nivel 3", "nombre_nivel": "Variables de Entorno Operacional", "area": "Logística", "variable": "puerto_salida", "justificacion": "Puerto puede asociarse a tiempos, infraestructura y rutas."},
{"nivel": "Nivel 3", "nombre_nivel": "Variables de Entorno Operacional", "area": "Contenedor", "variable": "tipo_contenedor", "justificacion": "Tipo de contenedor puede afectar conservación y exposición térmica."},
{"nivel": "Nivel 3", "nombre_nivel": "Variables de Entorno Operacional", "area": "Temporal", "variable": "temporada", "justificacion": "Captura cambios productivos, comerciales y climáticos entre temporadas."},
{"nivel": "Nivel 3", "nombre_nivel": "Variables de Entorno Operacional", "area": "Temporal", "variable": "cosecha_temprana_flag", "justificacion": "Cosecha temprana puede asociarse a madurez funcional menor."},
{"nivel": "Nivel 3", "nombre_nivel": "Variables de Entorno Operacional", "area": "Temporal", "variable": "cosecha_tardia_flag", "justificacion": "Cosecha tardía puede asociarse a sobremadurez o sensibilidad."},
# ==================================================================================
# NIVEL 4 — VARIABLES DE RIESGO HISTÓRICO Y COMERCIAL ACUMULADO
# ==================================================================================
{"nivel": "Nivel 4", "nombre_nivel": "Variables de Riesgo Histórico y Comercial Acumulado", "area": "Históricos", "variable": "tasa_reclamo_cliente_historica", "justificacion": "Historial de reclamos del cliente sin mirar información futura."},
{"nivel": "Nivel 4", "nombre_nivel": "Variables de Riesgo Histórico y Comercial Acumulado", "area": "Históricos", "variable": "tasa_reclamo_mercado_historica", "justificacion": "Historial de reclamos por macro mercado."},
{"nivel": "Nivel 4", "nombre_nivel": "Variables de Riesgo Histórico y Comercial Acumulado", "area": "Históricos", "variable": "tasa_reclamo_pais_historica", "justificacion": "Historial de reclamos por país destino."},
{"nivel": "Nivel 4", "nombre_nivel": "Variables de Riesgo Histórico y Comercial Acumulado", "area": "Históricos", "variable": "tasa_reclamo_packhouse_historica", "justificacion": "Historial de reclamos asociado a packhouse."},
{"nivel": "Nivel 4", "nombre_nivel": "Variables de Riesgo Histórico y Comercial Acumulado", "area": "Históricos", "variable": "tasa_reclamo_naviera_historica", "justificacion": "Historial de reclamos asociado a naviera."},
{"nivel": "Nivel 4", "nombre_nivel": "Variables de Riesgo Histórico y Comercial Acumulado", "area": "Históricos", "variable": "monto_promedio_reclamo_cliente", "justificacion": "Monto histórico promedio asociado al cliente."},
{"nivel": "Nivel 4", "nombre_nivel": "Variables de Riesgo Histórico y Comercial Acumulado", "area": "Históricos", "variable": "monto_promedio_reclamo_mercado", "justificacion": "Monto histórico promedio asociado al mercado."},
{"nivel": "Nivel 4", "nombre_nivel": "Variables de Riesgo Histórico y Comercial Acumulado", "area": "Históricos", "variable": "monto_promedio_reclamo_naviera", "justificacion": "Monto histórico promedio asociado a naviera."},
# ==================================================================================
# NIVEL 5 — VARIABLES DE CAUSALIDAD EXPANDIDA E INTERACCIONES
# ==================================================================================
{"nivel": "Nivel 5", "nombre_nivel": "Variables de Causalidad Expandida e Interacciones", "area": "Interacciones", "variable": "materia_seca_x_transito", "justificacion": "Interacción entre madurez y exposición logística."},
{"nivel": "Nivel 5", "nombre_nivel": "Variables de Causalidad Expandida e Interacciones", "area": "Interacciones", "variable": "materia_seca_x_mercado", "justificacion": "Interacción entre madurez y exigencia de mercado."},
{"nivel": "Nivel 5", "nombre_nivel": "Variables de Causalidad Expandida e Interacciones", "area": "Interacciones", "variable": "firmeza_x_transito", "justificacion": "Interacción entre firmeza y duración logística."},
{"nivel": "Nivel 5", "nombre_nivel": "Variables de Causalidad Expandida e Interacciones", "area": "Interacciones", "variable": "frio_x_transito", "justificacion": "Interacción entre quiebre de frío y duración de tránsito."},
{"nivel": "Nivel 5", "nombre_nivel": "Variables de Causalidad Expandida e Interacciones", "area": "Interacciones", "variable": "ndvi_x_subzona", "justificacion": "Interacción entre vigor del origen y zona agroclimática."},
]
audit_variables = pd.DataFrame(variables_estrategicas)
# --------------------------------------------------------------------------------------
# Validar existencia
# --------------------------------------------------------------------------------------
audit_variables["existe_en_base"] = audit_variables["variable"].apply(variable_exists)
audit_variables["estado"] = np.where(
audit_variables["existe_en_base"],
"Disponible",
"Gap de información"
)
audit_variables["prioridad_modelo"] = np.where(
audit_variables["existe_en_base"],
"Candidata inmediata",
"Requiere captura futura"
)
display(audit_variables)
# --------------------------------------------------------------------------------------
# Resumen general
# --------------------------------------------------------------------------------------
resumen_nivel = (
audit_variables
.groupby(["nivel", "nombre_nivel", "estado"])
.size()
.reset_index(name="cantidad")
)
display(resumen_nivel)
# --------------------------------------------------------------------------------------
# Matriz Nivel x Área
# --------------------------------------------------------------------------------------
matriz_nivel_area = pd.crosstab(
audit_variables["nivel"],
audit_variables["area"]
)
display(matriz_nivel_area)
# --------------------------------------------------------------------------------------
# Resumen Nivel 1 — Variables críticas
# --------------------------------------------------------------------------------------
nivel1 = audit_variables[audit_variables["nivel"] == "Nivel 1"].copy()
total_n1 = len(nivel1)
disp_n1 = (nivel1["estado"] == "Disponible").sum()
falt_n1 = (nivel1["estado"] == "Gap de información").sum()
pct_disp_n1 = disp_n1 / total_n1 * 100
pct_falt_n1 = falt_n1 / total_n1 * 100
print("\n" + "="*100)
print("RESUMEN VARIABLES CRÍTICAS — NIVEL 1")
print("="*100)
print(f"Variables críticas disponibles: {disp_n1} de {total_n1} ({pct_disp_n1:.1f}%)")
print(f"Variables críticas faltantes: {falt_n1} de {total_n1} ({pct_falt_n1:.1f}%)")
# --------------------------------------------------------------------------------------
# Brecha de captura de información
# --------------------------------------------------------------------------------------
gaps = audit_variables[audit_variables["estado"] == "Gap de información"].copy()
gap_area = (
gaps
.groupby("area")
.size()
.reset_index(name="cantidad_variables")
.sort_values("cantidad_variables", ascending=False)
)
display(gap_area)
print("\n" + "="*100)
print("BRECHA DE CAPTURA DE INFORMACIÓN")
print("="*100)
if gap_area.empty:
print("No se detectaron brechas de captura de información.")
else:
print("Las variables faltantes corresponden principalmente a:")
for _, r in gap_area.iterrows():
print(f"- {r['area']} ({r['cantidad_variables']} variables)")
print("""
Interpretación:
La auditoría estratégica permite separar variables disponibles de variables críticas aún no capturadas.
Las variables con estado 'Gap de información' no deben forzarse en el modelo actual.
Deben quedar documentadas como brechas de captura para futuras temporadas.
""")
# --------------------------------------------------------------------------------------
# Gráficos
# --------------------------------------------------------------------------------------
plt.figure(figsize=(8, 5))
audit_variables["estado"].value_counts().plot(kind="bar")
plt.title("Disponibilidad de variables estratégicas")
plt.ylabel("Cantidad de variables")
plt.xticks(rotation=0)
plt.tight_layout()
plt.show()
plt.figure(figsize=(10, 5))
(
audit_variables
.groupby(["nivel", "estado"])
.size()
.unstack(fill_value=0)
.plot(kind="bar", stacked=True, figsize=(10, 5))
)
plt.title("Disponibilidad de variables por nivel")
plt.ylabel("Cantidad de variables")
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
plt.figure(figsize=(12, 6))
plt.imshow(matriz_nivel_area, aspect="auto")
plt.title("Matriz Nivel x Área de Variables")
plt.xticks(range(len(matriz_nivel_area.columns)), matriz_nivel_area.columns, rotation=45, ha="right")
plt.yticks(range(len(matriz_nivel_area.index)), matriz_nivel_area.index)
plt.colorbar(label="Cantidad de variables")
plt.tight_layout()
plt.show()
# --------------------------------------------------------------------------------------
# Exportar
# --------------------------------------------------------------------------------------
audit_variables.to_csv(OUTPUT_DIR / "auditoria_estrategica_variables_5_niveles.csv", index=False)
resumen_nivel.to_csv(OUTPUT_DIR / "resumen_disponibilidad_variables_por_nivel.csv", index=False)
matriz_nivel_area.to_csv(OUTPUT_DIR / "matriz_nivel_area_variables.csv")
print("[OUTPUT] Archivos de auditoría estratégica exportados.")
==================================================================================================== AUDITORÍA ESTRATÉGICA DE VARIABLES — 5 NIVELES ====================================================================================================
| nivel | nombre_nivel | area | variable | justificacion | existe_en_base | estado | prioridad_modelo | |
|---|---|---|---|---|---|---|---|---|
| 0 | Nivel 1 | Variables Causales Directas | Madurez | materia_seca_pct | Indicador maestro de madurez funcional de la p... | True | Disponible | Candidata inmediata |
| 1 | Nivel 1 | Variables Causales Directas | Madurez | desviacion_materia_seca_pct | Captura heterogeneidad de madurez; puede gener... | True | Disponible | Candidata inmediata |
| 2 | Nivel 1 | Variables Causales Directas | Madurez | riesgo_heterogeneidad_score | Score derivado para cuantificar riesgo de mezc... | True | Disponible | Candidata inmediata |
| 3 | Nivel 1 | Variables Causales Directas | Frío | quiebre_cadena_frio_h | Mide exposición térmica fuera de rango; puede ... | True | Disponible | Candidata inmediata |
| 4 | Nivel 1 | Variables Causales Directas | Frío | tiempo_preenfriado_h | Demora en remover calor de campo aumenta riesg... | True | Disponible | Candidata inmediata |
| 5 | Nivel 1 | Variables Causales Directas | Frío | temperatura_setpoint_frio_c | Temperatura objetivo debe ser coherente con ma... | True | Disponible | Candidata inmediata |
| 6 | Nivel 1 | Variables Causales Directas | Logística | transito_real_dias | Mayor tránsito aumenta exposición a senescenci... | False | Gap de información | Requiere captura futura |
| 7 | Nivel 1 | Variables Causales Directas | Origen | indice_vigor_ndvi_base | Vigor vegetativo se asocia a condición fisioló... | True | Disponible | Candidata inmediata |
| 8 | Nivel 1 | Variables Causales Directas | Origen | subzona_agroclimatica | Define condiciones agroclimáticas y ventana de... | True | Disponible | Candidata inmediata |
| 9 | Nivel 1 | Variables Causales Directas | Origen | uniformidad_riego_pct | Variabilidad de riego puede generar heterogene... | True | Disponible | Candidata inmediata |
| 10 | Nivel 2 | Variables Explicativas Primarias | Origen | edad_arboles_anos | Edad del huerto puede influir en vigor, produc... | True | Disponible | Candidata inmediata |
| 11 | Nivel 2 | Variables Explicativas Primarias | Origen | densidad_arboles_ha | Densidad afecta competencia, luz, vigor y desa... | True | Disponible | Candidata inmediata |
| 12 | Nivel 2 | Variables Explicativas Primarias | Origen | portainjerto | Portainjerto puede influir en vigor, absorción... | True | Disponible | Candidata inmediata |
| 13 | Nivel 2 | Variables Explicativas Primarias | Origen | ph_suelo | pH afecta disponibilidad nutricional y condici... | True | Disponible | Candidata inmediata |
| 14 | Nivel 2 | Variables Explicativas Primarias | Origen | mo_pct | Materia orgánica se relaciona con estructura d... | True | Disponible | Candidata inmediata |
| 15 | Nivel 2 | Variables Explicativas Primarias | Clima | tmin_c | Temperatura mínima precosecha afecta metabolis... | True | Disponible | Candidata inmediata |
| 16 | Nivel 2 | Variables Explicativas Primarias | Clima | tmax_c | Temperatura máxima puede generar estrés térmico. | True | Disponible | Candidata inmediata |
| 17 | Nivel 2 | Variables Explicativas Primarias | Clima | lluvia_mm | Lluvia precosecha puede afectar condición sani... | True | Disponible | Candidata inmediata |
| 18 | Nivel 2 | Variables Explicativas Primarias | Clima | horas_frio_bajo_7c | Horas de frío pueden influir en fisiología y c... | True | Disponible | Candidata inmediata |
| 19 | Nivel 2 | Variables Explicativas Primarias | Clima | horas_sobre_30c | Horas sobre 30°C capturan estrés por calor. | True | Disponible | Candidata inmediata |
| 20 | Nivel 2 | Variables Explicativas Primarias | Cosecha | dias_muestreo_a_cosecha | Ventana entre muestreo y cosecha afecta repres... | True | Disponible | Candidata inmediata |
| 21 | Nivel 2 | Variables Explicativas Primarias | Cosecha | semana_cosecha | Captura momento fenológico y etapa de temporada. | False | Gap de información | Requiere captura futura |
| 22 | Nivel 2 | Variables Explicativas Primarias | Cosecha | mes_cosecha | Permite evaluar estacionalidad de cosecha. | False | Gap de información | Requiere captura futura |
| 23 | Nivel 2 | Variables Explicativas Primarias | Packing | dias_cosecha_a_packing | Tiempo desde cosecha a packing afecta condició... | True | Disponible | Candidata inmediata |
| 24 | Nivel 2 | Variables Explicativas Primarias | Madurez | firmeza_pulpa_lb | Resistencia estructural del fruto frente a dañ... | True | Disponible | Candidata inmediata |
| 25 | Nivel 2 | Variables Explicativas Primarias | Madurez | calibre | Tamaño del fruto puede relacionarse con madure... | True | Disponible | Candidata inmediata |
| 26 | Nivel 2 | Variables Explicativas Primarias | Madurez | peso_fruto_g | Peso captura condición física y calibre efectivo. | True | Disponible | Candidata inmediata |
| 27 | Nivel 2 | Variables Explicativas Primarias | Logística | retraso_logistico_flag | Retrasos aumentan exposición a deterioro y rie... | False | Gap de información | Requiere captura futura |
| 28 | Nivel 2 | Variables Explicativas Primarias | Logística | diferencia_transito_dias | Diferencia entre tránsito real y planificado. | False | Gap de información | Requiere captura futura |
| 29 | Nivel 2 | Variables Explicativas Primarias | Contenedor | atmosfera_controlada | Condición de atmósfera afecta conservación y m... | True | Disponible | Candidata inmediata |
| 30 | Nivel 3 | Variables de Entorno Operacional | Mercado | pais_destino | Destino puede reflejar distancia, exigencia co... | True | Disponible | Candidata inmediata |
| 31 | Nivel 3 | Variables de Entorno Operacional | Mercado | macro_mercado | Agrupa destinos con comportamiento logístico y... | True | Disponible | Candidata inmediata |
| 32 | Nivel 3 | Variables de Entorno Operacional | Mercado | canal_destino | Canal puede reflejar exigencia de cliente y ni... | True | Disponible | Candidata inmediata |
| 33 | Nivel 3 | Variables de Entorno Operacional | Cliente | cliente_tipo | Tipo de cliente puede tener diferente estándar... | True | Disponible | Candidata inmediata |
| 34 | Nivel 3 | Variables de Entorno Operacional | Packing | packhouse_id | Captura diferencias operacionales entre planta... | True | Disponible | Candidata inmediata |
| 35 | Nivel 3 | Variables de Entorno Operacional | Packing | linea_packing | Diferencias de línea pueden afectar manipulaci... | True | Disponible | Candidata inmediata |
| 36 | Nivel 3 | Variables de Entorno Operacional | Packing | operador_packing | Operador puede reflejar diferencias operativas... | True | Disponible | Candidata inmediata |
| 37 | Nivel 3 | Variables de Entorno Operacional | Logística | naviera | Naviera captura diferencias de servicio, ruta ... | True | Disponible | Candidata inmediata |
| 38 | Nivel 3 | Variables de Entorno Operacional | Logística | puerto_salida | Puerto puede asociarse a tiempos, infraestruct... | True | Disponible | Candidata inmediata |
| 39 | Nivel 3 | Variables de Entorno Operacional | Contenedor | tipo_contenedor | Tipo de contenedor puede afectar conservación ... | True | Disponible | Candidata inmediata |
| 40 | Nivel 3 | Variables de Entorno Operacional | Temporal | temporada | Captura cambios productivos, comerciales y cli... | True | Disponible | Candidata inmediata |
| 41 | Nivel 3 | Variables de Entorno Operacional | Temporal | cosecha_temprana_flag | Cosecha temprana puede asociarse a madurez fun... | False | Gap de información | Requiere captura futura |
| 42 | Nivel 3 | Variables de Entorno Operacional | Temporal | cosecha_tardia_flag | Cosecha tardía puede asociarse a sobremadurez ... | False | Gap de información | Requiere captura futura |
| 43 | Nivel 4 | Variables de Riesgo Histórico y Comercial Acum... | Históricos | tasa_reclamo_cliente_historica | Historial de reclamos del cliente sin mirar in... | False | Gap de información | Requiere captura futura |
| 44 | Nivel 4 | Variables de Riesgo Histórico y Comercial Acum... | Históricos | tasa_reclamo_mercado_historica | Historial de reclamos por macro mercado. | False | Gap de información | Requiere captura futura |
| 45 | Nivel 4 | Variables de Riesgo Histórico y Comercial Acum... | Históricos | tasa_reclamo_pais_historica | Historial de reclamos por país destino. | True | Disponible | Candidata inmediata |
| 46 | Nivel 4 | Variables de Riesgo Histórico y Comercial Acum... | Históricos | tasa_reclamo_packhouse_historica | Historial de reclamos asociado a packhouse. | False | Gap de información | Requiere captura futura |
| 47 | Nivel 4 | Variables de Riesgo Histórico y Comercial Acum... | Históricos | tasa_reclamo_naviera_historica | Historial de reclamos asociado a naviera. | True | Disponible | Candidata inmediata |
| 48 | Nivel 4 | Variables de Riesgo Histórico y Comercial Acum... | Históricos | monto_promedio_reclamo_cliente | Monto histórico promedio asociado al cliente. | False | Gap de información | Requiere captura futura |
| 49 | Nivel 4 | Variables de Riesgo Histórico y Comercial Acum... | Históricos | monto_promedio_reclamo_mercado | Monto histórico promedio asociado al mercado. | False | Gap de información | Requiere captura futura |
| 50 | Nivel 4 | Variables de Riesgo Histórico y Comercial Acum... | Históricos | monto_promedio_reclamo_naviera | Monto histórico promedio asociado a naviera. | True | Disponible | Candidata inmediata |
| 51 | Nivel 5 | Variables de Causalidad Expandida e Interacciones | Interacciones | materia_seca_x_transito | Interacción entre madurez y exposición logística. | False | Gap de información | Requiere captura futura |
| 52 | Nivel 5 | Variables de Causalidad Expandida e Interacciones | Interacciones | materia_seca_x_mercado | Interacción entre madurez y exigencia de mercado. | False | Gap de información | Requiere captura futura |
| 53 | Nivel 5 | Variables de Causalidad Expandida e Interacciones | Interacciones | firmeza_x_transito | Interacción entre firmeza y duración logística. | False | Gap de información | Requiere captura futura |
| 54 | Nivel 5 | Variables de Causalidad Expandida e Interacciones | Interacciones | frio_x_transito | Interacción entre quiebre de frío y duración d... | False | Gap de información | Requiere captura futura |
| 55 | Nivel 5 | Variables de Causalidad Expandida e Interacciones | Interacciones | ndvi_x_subzona | Interacción entre vigor del origen y zona agro... | False | Gap de información | Requiere captura futura |
| nivel | nombre_nivel | estado | cantidad | |
|---|---|---|---|---|
| 0 | Nivel 1 | Variables Causales Directas | Disponible | 9 |
| 1 | Nivel 1 | Variables Causales Directas | Gap de información | 1 |
| 2 | Nivel 2 | Variables Explicativas Primarias | Disponible | 16 |
| 3 | Nivel 2 | Variables Explicativas Primarias | Gap de información | 4 |
| 4 | Nivel 3 | Variables de Entorno Operacional | Disponible | 11 |
| 5 | Nivel 3 | Variables de Entorno Operacional | Gap de información | 2 |
| 6 | Nivel 4 | Variables de Riesgo Histórico y Comercial Acum... | Disponible | 3 |
| 7 | Nivel 4 | Variables de Riesgo Histórico y Comercial Acum... | Gap de información | 5 |
| 8 | Nivel 5 | Variables de Causalidad Expandida e Interacciones | Gap de información | 5 |
| area | Cliente | Clima | Contenedor | Cosecha | Frío | Históricos | Interacciones | Logística | Madurez | Mercado | Origen | Packing | Temporal |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| nivel | |||||||||||||
| Nivel 1 | 0 | 0 | 0 | 0 | 3 | 0 | 0 | 1 | 3 | 0 | 3 | 0 | 0 |
| Nivel 2 | 0 | 5 | 1 | 3 | 0 | 0 | 0 | 2 | 3 | 0 | 5 | 1 | 0 |
| Nivel 3 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 2 | 0 | 3 | 0 | 3 | 3 |
| Nivel 4 | 0 | 0 | 0 | 0 | 0 | 8 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Nivel 5 | 0 | 0 | 0 | 0 | 0 | 0 | 5 | 0 | 0 | 0 | 0 | 0 | 0 |
==================================================================================================== RESUMEN VARIABLES CRÍTICAS — NIVEL 1 ==================================================================================================== Variables críticas disponibles: 9 de 10 (90.0%) Variables críticas faltantes: 1 de 10 (10.0%)
| area | cantidad_variables | |
|---|---|---|
| 1 | Históricos | 5 |
| 2 | Interacciones | 5 |
| 3 | Logística | 3 |
| 0 | Cosecha | 2 |
| 4 | Temporal | 2 |
==================================================================================================== BRECHA DE CAPTURA DE INFORMACIÓN ==================================================================================================== Las variables faltantes corresponden principalmente a: - Históricos (5 variables) - Interacciones (5 variables) - Logística (3 variables) - Cosecha (2 variables) - Temporal (2 variables) Interpretación: La auditoría estratégica permite separar variables disponibles de variables críticas aún no capturadas. Las variables con estado 'Gap de información' no deben forzarse en el modelo actual. Deben quedar documentadas como brechas de captura para futuras temporadas.
<Figure size 1000x500 with 0 Axes>
[OUTPUT] Archivos de auditoría estratégica exportados.
6. Punto 2 — auditoria causal sin modelo¶
Esta sección responde las preguntas estratégicas A–H:
A. ¿Dónde ocurren los reclamos?
B. ¿Cuándo ocurren?
C. ¿Con quién ocurren?
D. ¿Qué variables muestran asociación?
E. ¿Materia seca explica reclamos?
F. ¿Desviación de materia seca explica reclamos?
G. ¿Tránsito explica reclamos?
H. ¿Origen explica reclamos?
# ======================================================================================
# 6. FUNCIONES DE auditoria CAUSAL / ASOCIATIVO SIN MODELO
# ======================================================================================
print_step("PUNTO 2 — AUDITORÍA CAUSAL SIN MODELO")
analysis_df = base_enriq.copy()
analysis_df[TARGET] = pd.to_numeric(analysis_df[TARGET], errors="coerce").fillna(0).astype(int)
# Función para tabla de riesgo por variable categórica.
def risk_by_category(df, col, min_n=20, top=15):
if col not in df.columns:
print(f"[INFO] Variable no disponible: {col}")
return pd.DataFrame()
tbl = (
df.groupby(col, dropna=False)
.agg(
contenedores=(TARGET, "count"),
reclamos=(TARGET, "sum"),
monto_reclamo_usd=("monto_reclamo_usd", "sum") if "monto_reclamo_usd" in df.columns else (TARGET, "sum")
)
.reset_index()
)
tbl["tasa_reclamo"] = tbl["reclamos"] / tbl["contenedores"]
tbl = tbl[tbl["contenedores"] >= min_n].sort_values(["tasa_reclamo", "reclamos"], ascending=False)
return tbl.head(top)
# Función para segmentar variables numéricas en bins y calcular tasa.
def risk_by_numeric_bins(df, col, bins=None, q=5):
if col not in df.columns:
print(f"[INFO] Variable numérica no disponible: {col}")
return pd.DataFrame()
tmp = df[[col, TARGET, "monto_reclamo_usd"]].copy() if "monto_reclamo_usd" in df.columns else df[[col, TARGET]].copy()
tmp[col] = pd.to_numeric(tmp[col], errors="coerce")
tmp = tmp.dropna(subset=[col])
if tmp.empty:
return pd.DataFrame()
if bins is not None:
tmp["segmento"] = pd.cut(tmp[col], bins=bins, include_lowest=True)
else:
# qcut con duplicados controlados.
tmp["segmento"] = pd.qcut(tmp[col], q=q, duplicates="drop")
agg_dict = {
"contenedores": (TARGET, "count"),
"reclamos": (TARGET, "sum"),
}
if "monto_reclamo_usd" in tmp.columns:
agg_dict["monto_reclamo_usd"] = ("monto_reclamo_usd", "sum")
tbl = tmp.groupby("segmento", observed=True).agg(**agg_dict).reset_index()
tbl["tasa_reclamo"] = tbl["reclamos"] / tbl["contenedores"]
return tbl
# Función para gráfico de riesgo categórico.
def plot_risk_category(tbl, col, title, filename):
if tbl.empty:
return
plot_tbl = tbl.sort_values("tasa_reclamo")
plt.figure(figsize=(10, max(4, len(plot_tbl)*0.35)))
plt.barh(plot_tbl[col].astype(str), plot_tbl["tasa_reclamo"]*100)
plt.title(title)
plt.xlabel("Tasa de reclamo (%)")
plt.ylabel(col)
savefig(filename)
# Función para gráfico de bins.
def plot_risk_bins(tbl, title, filename):
if tbl.empty:
return
plt.figure(figsize=(10,4))
plt.plot(tbl["segmento"].astype(str), tbl["tasa_reclamo"]*100, marker="o")
plt.title(title)
plt.xlabel("Segmento")
plt.ylabel("Tasa de reclamo (%)")
plt.xticks(rotation=30, ha="right")
savefig(filename)
==================================================================================================== PUNTO 2 — AUDITORÍA CAUSAL SIN MODELO ====================================================================================================
# ======================================================================================
# 6A. ¿DÓNDE OCURREN LOS RECLAMOS?
# ======================================================================================
print_step("6A — ¿DÓNDE OCURREN LOS RECLAMOS?")
segmentos_donde = ["pais_destino", "macro_mercado", "canal_destino", "puerto_salida", "subzona_agroclimatica", "comuna"]
segment_tables = {}
for seg in segmentos_donde:
tbl = risk_by_category(analysis_df, seg, min_n=20, top=15)
if not tbl.empty:
segment_tables[seg] = tbl
print(f"\nTop riesgo por {seg}:")
display(tbl)
tbl.to_csv(OUTPUT_DIR / f"riesgo_por_{seg}_v40.csv", index=False)
plot_risk_category(tbl, seg, f"Tasa de reclamo por {seg}", f"05_riesgo_por_{seg}.png")
print("Lectura:")
print("Esta sección responde dónde se concentra el reclamo: país, macro mercado, canal, puerto o subzona agroclimática.")
==================================================================================================== 6A — ¿DÓNDE OCURREN LOS RECLAMOS? ==================================================================================================== [INFO] Variable no disponible: pais_destino Top riesgo por macro_mercado:
| macro_mercado | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 1 | EU | 9743 | 148 | 189320.0 | 0.015190 |
| 0 | DOM | 2655 | 5 | 4828.0 | 0.001883 |
| 2 | LATAM | 715 | 1 | 1111.0 | 0.001399 |
| 3 | NA | 1623 | 0 | 0.0 | 0.000000 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/05_riesgo_por_macro_mercado.png
Top riesgo por canal_destino:
| canal_destino | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 0 | exportacion | 12081 | 149 | 190431.0 | 0.012333 |
| 1 | mercado_local | 2655 | 5 | 4828.0 | 0.001883 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/05_riesgo_por_canal_destino.png
Top riesgo por puerto_salida:
| puerto_salida | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 1 | San Antonio | 6711 | 89 | 119726.0 | 0.013262 |
| 2 | Valparaiso | 5370 | 60 | 70705.0 | 0.011173 |
| 0 | N/A | 2655 | 5 | 4828.0 | 0.001883 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/05_riesgo_por_puerto_salida.png
Top riesgo por subzona_agroclimatica:
| subzona_agroclimatica | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 0 | Lago Rapel | 2419 | 29 | 35481.0 | 0.011988 |
| 3 | Valle Interior | 4909 | 53 | 67028.0 | 0.010796 |
| 2 | Secano Interior | 4902 | 49 | 65636.0 | 0.009996 |
| 1 | Secano Costero | 2506 | 23 | 27114.0 | 0.009178 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/05_riesgo_por_subzona_agroclimatica.png
Top riesgo por comuna:
| comuna | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 1 | Las Cabras | 2419 | 29 | 35481.0 | 0.011988 |
| 2 | Litueche | 2520 | 29 | 39198.0 | 0.011508 |
| 5 | Pichidegua | 2454 | 27 | 34078.0 | 0.011002 |
| 4 | Peumo | 2455 | 26 | 32950.0 | 0.010591 |
| 0 | La Estrella | 2506 | 23 | 27114.0 | 0.009178 |
| 3 | Marchihue | 2382 | 20 | 26438.0 | 0.008396 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/05_riesgo_por_comuna.png
Lectura: Esta sección responde dónde se concentra el reclamo: país, macro mercado, canal, puerto o subzona agroclimática.
# ======================================================================================
# 6B. ¿CUÁNDO OCURREN LOS RECLAMOS?
# ======================================================================================
print_step("6B — ¿CUÁNDO OCURREN LOS RECLAMOS?")
segmentos_cuando = ["temporada", "mes_cosecha", "semana_cosecha", "mes_embarque", "semana_embarque"]
for seg in segmentos_cuando:
tbl = risk_by_category(analysis_df, seg, min_n=1, top=30)
if not tbl.empty:
print(f"\nRiesgo por {seg}:")
display(tbl)
tbl.to_csv(OUTPUT_DIR / f"riesgo_por_{seg}_v40.csv", index=False)
# Para variables de tiempo, graficar ordenado por categoría.
plot_tbl = tbl.copy().sort_values(seg)
plt.figure(figsize=(10,4))
plt.plot(plot_tbl[seg].astype(str), plot_tbl["tasa_reclamo"]*100, marker="o")
plt.title(f"Tasa de reclamo por {seg}")
plt.xlabel(seg)
plt.ylabel("Tasa de reclamo (%)")
plt.xticks(rotation=30, ha="right")
savefig(f"06_riesgo_por_{seg}.png")
print("Lectura:")
print("Esta sección permite detectar ventanas temporales críticas: temporadas, meses o semanas con mayor tasa de reclamo.")
==================================================================================================== 6B — ¿CUÁNDO OCURREN LOS RECLAMOS? ==================================================================================================== Riesgo por temporada:
| temporada | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 6 | 2024_2025 | 2114 | 29 | 32617.0 | 0.013718 |
| 2 | 2020_2021 | 2157 | 28 | 34319.0 | 0.012981 |
| 5 | 2023_2024 | 2093 | 24 | 31353.0 | 0.011467 |
| 1 | 2019_2020 | 2046 | 21 | 30357.0 | 0.010264 |
| 0 | 2018_2019 | 2108 | 21 | 24394.0 | 0.009962 |
| 4 | 2022_2023 | 2084 | 16 | 21306.0 | 0.007678 |
| 3 | 2021_2022 | 2134 | 15 | 20913.0 | 0.007029 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/06_riesgo_por_temporada.png
Riesgo por mes_cosecha:
| mes_cosecha | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 1 | 2 | 1884 | 34 | 47740.0 | 0.018047 |
| 0 | 1 | 2212 | 30 | 38400.0 | 0.013562 |
| 3 | 9 | 2011 | 22 | 25823.0 | 0.010940 |
| 6 | 12 | 2223 | 22 | 28219.0 | 0.009897 |
| 2 | 8 | 2146 | 18 | 21192.0 | 0.008388 |
| 5 | 11 | 2101 | 16 | 19455.0 | 0.007615 |
| 4 | 10 | 2159 | 12 | 14430.0 | 0.005558 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/06_riesgo_por_mes_cosecha.png
Riesgo por semana_cosecha:
| semana_cosecha | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 7 | 8.0 | 455 | 10 | 13789.0 | 0.021978 |
| 32 | 53.0 | 95 | 2 | 4091.0 | 0.021053 |
| 6 | 7.0 | 521 | 10 | 15012.0 | 0.019194 |
| 2 | 3.0 | 495 | 9 | 10379.0 | 0.018182 |
| 0 | 1.0 | 505 | 9 | 11102.0 | 0.017822 |
| 17 | 38.0 | 459 | 8 | 10781.0 | 0.017429 |
| 5 | 6.0 | 446 | 7 | 9543.0 | 0.015695 |
| 4 | 5.0 | 514 | 8 | 10964.0 | 0.015564 |
| 29 | 50.0 | 518 | 7 | 8432.0 | 0.013514 |
| 24 | 45.0 | 463 | 6 | 7185.0 | 0.012959 |
| 8 | 9.0 | 157 | 2 | 2398.0 | 0.012739 |
| 30 | 51.0 | 487 | 6 | 7761.0 | 0.012320 |
| 28 | 49.0 | 500 | 6 | 7113.0 | 0.012000 |
| 10 | 31.0 | 348 | 4 | 5304.0 | 0.011494 |
| 12 | 33.0 | 449 | 5 | 5213.0 | 0.011136 |
| 18 | 39.0 | 523 | 5 | 5707.0 | 0.009560 |
| 15 | 36.0 | 438 | 4 | 3609.0 | 0.009132 |
| 3 | 4.0 | 476 | 4 | 6877.0 | 0.008403 |
| 21 | 42.0 | 479 | 4 | 5756.0 | 0.008351 |
| 11 | 32.0 | 489 | 4 | 4668.0 | 0.008180 |
| 13 | 34.0 | 496 | 4 | 5221.0 | 0.008065 |
| 25 | 46.0 | 504 | 4 | 4619.0 | 0.007937 |
| 26 | 47.0 | 505 | 4 | 5424.0 | 0.007921 |
| 1 | 2.0 | 519 | 4 | 4453.0 | 0.007707 |
| 16 | 37.0 | 445 | 3 | 4384.0 | 0.006742 |
| 19 | 40.0 | 480 | 3 | 1649.0 | 0.006250 |
| 31 | 52.0 | 486 | 3 | 3453.0 | 0.006173 |
| 20 | 41.0 | 501 | 3 | 3696.0 | 0.005988 |
| 14 | 35.0 | 492 | 2 | 1804.0 | 0.004065 |
| 22 | 43.0 | 522 | 2 | 2172.0 | 0.003831 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/06_riesgo_por_semana_cosecha.png
Riesgo por mes_embarque:
| mes_embarque | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 2 | 3 | 1014 | 27 | 40433.0 | 0.026627 |
| 1 | 2 | 1974 | 28 | 34975.0 | 0.014184 |
| 0 | 1 | 2224 | 24 | 30656.0 | 0.010791 |
| 6 | 10 | 2183 | 19 | 22157.0 | 0.008704 |
| 4 | 8 | 1071 | 9 | 10235.0 | 0.008403 |
| 8 | 12 | 2183 | 17 | 20085.0 | 0.007787 |
| 5 | 9 | 1969 | 15 | 17276.0 | 0.007618 |
| 7 | 11 | 2117 | 15 | 19442.0 | 0.007085 |
| 3 | 4 | 1 | 0 | 0.0 | 0.000000 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/06_riesgo_por_mes_embarque.png
Riesgo por semana_embarque:
| semana_embarque | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 12 | 13.0 | 14 | 1 | 1579.0 | 0.071429 |
| 10 | 11.0 | 272 | 13 | 19011.0 | 0.047794 |
| 9 | 10.0 | 384 | 8 | 11129.0 | 0.020833 |
| 8 | 9.0 | 456 | 9 | 12631.0 | 0.019737 |
| 1 | 2.0 | 504 | 8 | 10575.0 | 0.015873 |
| 4 | 5.0 | 527 | 8 | 9117.0 | 0.015180 |
| 26 | 43.0 | 538 | 8 | 7098.0 | 0.014870 |
| 23 | 40.0 | 473 | 7 | 9436.0 | 0.014799 |
| 6 | 7.0 | 482 | 7 | 9986.0 | 0.014523 |
| 3 | 4.0 | 520 | 7 | 9165.0 | 0.013462 |
| 18 | 35.0 | 450 | 6 | 7780.0 | 0.013333 |
| 17 | 34.0 | 381 | 5 | 5085.0 | 0.013123 |
| 31 | 48.0 | 460 | 6 | 7551.0 | 0.013043 |
| 5 | 6.0 | 470 | 6 | 7709.0 | 0.012766 |
| 36 | 53.0 | 81 | 1 | 1158.0 | 0.012346 |
| 11 | 12.0 | 86 | 1 | 1611.0 | 0.011628 |
| 2 | 3.0 | 509 | 5 | 7079.0 | 0.009823 |
| 22 | 39.0 | 430 | 4 | 4685.0 | 0.009302 |
| 24 | 41.0 | 477 | 4 | 5369.0 | 0.008386 |
| 30 | 47.0 | 486 | 4 | 5359.0 | 0.008230 |
| 7 | 8.0 | 487 | 4 | 5015.0 | 0.008214 |
| 20 | 37.0 | 491 | 4 | 3901.0 | 0.008147 |
| 35 | 52.0 | 502 | 4 | 4679.0 | 0.007968 |
| 27 | 44.0 | 514 | 4 | 4576.0 | 0.007782 |
| 33 | 50.0 | 464 | 3 | 2820.0 | 0.006466 |
| 0 | 1.0 | 473 | 3 | 3274.0 | 0.006342 |
| 32 | 49.0 | 507 | 3 | 3969.0 | 0.005917 |
| 34 | 51.0 | 518 | 3 | 2868.0 | 0.005792 |
| 19 | 36.0 | 453 | 2 | 2320.0 | 0.004415 |
| 25 | 42.0 | 479 | 2 | 2306.0 | 0.004175 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/06_riesgo_por_semana_embarque.png
Lectura: Esta sección permite detectar ventanas temporales críticas: temporadas, meses o semanas con mayor tasa de reclamo.
# ======================================================================================
# 6C. ¿CON QUIÉN OCURREN LOS RECLAMOS?
# ======================================================================================
print_step("6C — ¿CON QUIÉN OCURREN LOS RECLAMOS?")
segmentos_quien = ["cliente_tipo", "packhouse_id", "linea_packing", "operador_packing", "naviera", "tipo_contenedor"]
for seg in segmentos_quien:
tbl = risk_by_category(analysis_df, seg, min_n=20, top=15)
if not tbl.empty:
print(f"\nTop riesgo por {seg}:")
display(tbl)
tbl.to_csv(OUTPUT_DIR / f"riesgo_por_{seg}_v40.csv", index=False)
plot_risk_category(tbl, seg, f"Tasa de reclamo por {seg}", f"07_riesgo_por_{seg}.png")
print("Lectura:")
print("Esta sección identifica actores o unidades operativas asociadas a mayor riesgo: clientes, packhouse, líneas, navieras y contenedores.")
==================================================================================================== 6C — ¿CON QUIÉN OCURREN LOS RECLAMOS? ==================================================================================================== [INFO] Variable no disponible: cliente_tipo Top riesgo por packhouse_id:
| packhouse_id | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 2 | PK_Rapel_Norte | 2419 | 29 | 35481.0 | 0.011988 |
| 1 | PK_Rapel_Centro | 2454 | 27 | 34078.0 | 0.011002 |
| 0 | PK_Peumo | 2455 | 26 | 32950.0 | 0.010591 |
| 3 | PK_Secano | 7408 | 72 | 92750.0 | 0.009719 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/07_riesgo_por_packhouse_id.png
Top riesgo por linea_packing:
| linea_packing | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 0 | L1 | 4184 | 53 | 66437.0 | 0.012667 |
| 1 | L2 | 3797 | 43 | 56142.0 | 0.011325 |
| 3 | L4 | 3281 | 30 | 36365.0 | 0.009144 |
| 2 | L3 | 3474 | 28 | 36315.0 | 0.008060 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/07_riesgo_por_linea_packing.png
[INFO] Variable no disponible: operador_packing Top riesgo por naviera:
| naviera | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 1 | Hapag-Lloyd | 2365 | 36 | 42561.0 | 0.015222 |
| 0 | CMA CGM | 2411 | 32 | 41735.0 | 0.013273 |
| 2 | MSC | 2514 | 32 | 41468.0 | 0.012729 |
| 3 | Maersk | 2434 | 26 | 34095.0 | 0.010682 |
| 5 | ONE | 2357 | 23 | 30572.0 | 0.009758 |
| 4 | N/A | 2655 | 5 | 4828.0 | 0.001883 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/07_riesgo_por_naviera.png
Top riesgo por tipo_contenedor:
| tipo_contenedor | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 1 | reefer_20 | 1406 | 19 | 27651.0 | 0.013514 |
| 2 | reefer_40 | 10675 | 130 | 162780.0 | 0.012178 |
| 0 | camion_frio | 2655 | 5 | 4828.0 | 0.001883 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/07_riesgo_por_tipo_contenedor.png
Lectura: Esta sección identifica actores o unidades operativas asociadas a mayor riesgo: clientes, packhouse, líneas, navieras y contenedores.
# ======================================================================================
# 6D-H. VARIABLES NUMÉRICAS CON HIPÓTESIS CAUSAL
# ======================================================================================
print_step("6D-H — VARIABLES NUMÉRICAS CON HIPÓTESIS CAUSAL")
# E. Materia seca
numeric_analyses = {
"materia_seca_pct": [0, 22, 24, 26, 28, 100],
"desviacion_materia_seca_pct": [0, 2, 4, 6, 8, 100],
"firmeza_pulpa_lb": None,
"dias_cosecha_a_packing": None,
"transito_real_dias": [0, 15, 25, 35, 45, 200],
"diferencia_transito_dias": None,
"tmax_promedio_precosecha": None,
"lluvia_acumulada_precosecha": None,
"horas_sobre_30c_acumuladas": None,
"uniformidad_riego_pct": None,
"indice_vigor_ndvi_base": None,
"ndvi_base": None,
}
numeric_results = {}
for col, bins in numeric_analyses.items():
if col not in analysis_df.columns:
continue
tbl = risk_by_numeric_bins(analysis_df, col, bins=bins, q=5)
if not tbl.empty:
numeric_results[col] = tbl
print(f"\nRiesgo por segmentos de {col}:")
display(tbl)
tbl.to_csv(OUTPUT_DIR / f"riesgo_segmentos_{col}_v40.csv", index=False)
plot_risk_bins(tbl, f"Tasa de reclamo por segmentos de {col}", f"08_riesgo_segmentos_{col}.png")
# Boxplots comparativos para las variables clave.
for col in ["materia_seca_pct", "desviacion_materia_seca_pct", "firmeza_pulpa_lb", "transito_real_dias", "dias_cosecha_a_packing"]:
if col in analysis_df.columns:
tmp = analysis_df[[col, TARGET]].copy()
tmp[col] = pd.to_numeric(tmp[col], errors="coerce")
tmp = tmp.dropna(subset=[col])
if not tmp.empty:
plt.figure(figsize=(6,4))
data_box = [tmp.loc[tmp[TARGET]==0, col], tmp.loc[tmp[TARGET]==1, col]]
plt.boxplot(data_box, labels=["Sin reclamo", "Con reclamo"], showfliers=False)
plt.title(f"Distribución de {col} por reclamo")
plt.ylabel(col)
savefig(f"09_boxplot_{col}_por_reclamo.png")
print("Lectura:")
print("Estos gráficos no prueban causalidad por sí solos, pero muestran asociaciones compatibles con hipótesis fisiológicas y operacionales.")
==================================================================================================== 6D-H — VARIABLES NUMÉRICAS CON HIPÓTESIS CAUSAL ==================================================================================================== Riesgo por segmentos de materia_seca_pct:
| segmento | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 0 | (-0.001, 22.0] | 2537 | 21 | 26446.0 | 0.008277 |
| 1 | (22.0, 24.0] | 3043 | 25 | 28695.0 | 0.008216 |
| 2 | (24.0, 26.0] | 3094 | 25 | 30536.0 | 0.008080 |
| 3 | (26.0, 28.0] | 2820 | 27 | 35253.0 | 0.009574 |
| 4 | (28.0, 100.0] | 3242 | 56 | 74329.0 | 0.017273 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/08_riesgo_segmentos_materia_seca_pct.png
Riesgo por segmentos de desviacion_materia_seca_pct:
| segmento | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 0 | (-0.001, 2.0] | 14686 | 153 | 193592.0 | 0.010418 |
| 1 | (2.0, 4.0] | 50 | 1 | 1667.0 | 0.020000 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/08_riesgo_segmentos_desviacion_materia_seca_pct.png
Riesgo por segmentos de firmeza_pulpa_lb:
| segmento | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 0 | (18.999, 34.0] | 2965 | 54 | 74443.0 | 0.018212 |
| 1 | (34.0, 37.6] | 2984 | 23 | 27474.0 | 0.007708 |
| 2 | (37.6, 41.0] | 2950 | 27 | 34131.0 | 0.009153 |
| 3 | (41.0, 44.2] | 2934 | 31 | 35660.0 | 0.010566 |
| 4 | (44.2, 52.0] | 2903 | 19 | 23551.0 | 0.006545 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/08_riesgo_segmentos_firmeza_pulpa_lb.png
Riesgo por segmentos de dias_cosecha_a_packing:
| segmento | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 0 | (-0.001, 0.6] | 3429 | 25 | 31085.0 | 0.007291 |
| 1 | (0.6, 1.0] | 2833 | 31 | 34597.0 | 0.010942 |
| 2 | (1.0, 1.4] | 3093 | 28 | 36518.0 | 0.009053 |
| 3 | (1.4, 1.8] | 2608 | 33 | 43920.0 | 0.012653 |
| 4 | (1.8, 3.8] | 2773 | 37 | 49139.0 | 0.013343 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/08_riesgo_segmentos_dias_cosecha_a_packing.png
Riesgo por segmentos de tmax_promedio_precosecha:
| segmento | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 0 | (22.735999999999997, 23.355] | 4925 | 52 | 62595.0 | 0.010558 |
| 1 | (23.355, 23.524] | 2382 | 20 | 26438.0 | 0.008396 |
| 2 | (23.524, 23.531] | 2520 | 29 | 39198.0 | 0.011508 |
| 3 | (23.531, 23.874] | 2454 | 27 | 34078.0 | 0.011002 |
| 4 | (23.874, 23.941] | 2455 | 26 | 32950.0 | 0.010591 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/08_riesgo_segmentos_tmax_promedio_precosecha.png
Riesgo por segmentos de lluvia_acumulada_precosecha:
| segmento | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 0 | (2861.0989999999997, 3073.6] | 4909 | 53 | 67028.0 | 0.010796 |
| 1 | (3073.6, 3157.4] | 2419 | 29 | 35481.0 | 0.011988 |
| 2 | (3157.4, 3507.7] | 2520 | 29 | 39198.0 | 0.011508 |
| 3 | (3507.7, 3740.3] | 2382 | 20 | 26438.0 | 0.008396 |
| 4 | (3740.3, 3775.3] | 2506 | 23 | 27114.0 | 0.009178 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/08_riesgo_segmentos_lluvia_acumulada_precosecha.png
Riesgo por segmentos de horas_sobre_30c_acumuladas:
| segmento | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 0 | (1085.999, 1259.0] | 4925 | 52 | 62595.0 | 0.010558 |
| 1 | (1259.0, 1324.0] | 2520 | 29 | 39198.0 | 0.011508 |
| 2 | (1324.0, 1467.0] | 2382 | 20 | 26438.0 | 0.008396 |
| 3 | (1467.0, 1530.0] | 2454 | 27 | 34078.0 | 0.011002 |
| 4 | (1530.0, 1577.0] | 2455 | 26 | 32950.0 | 0.010591 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/08_riesgo_segmentos_horas_sobre_30c_acumuladas.png
Riesgo por segmentos de uniformidad_riego_pct:
| segmento | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 0 | (75.999, 79.4] | 2986 | 33 | 45668.0 | 0.011052 |
| 1 | (79.4, 82.6] | 2950 | 40 | 52135.0 | 0.013559 |
| 2 | (82.6, 87.0] | 2977 | 21 | 24203.0 | 0.007054 |
| 3 | (87.0, 91.7] | 2990 | 34 | 42446.0 | 0.011371 |
| 4 | (91.7, 96.0] | 2833 | 26 | 30807.0 | 0.009178 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/08_riesgo_segmentos_uniformidad_riego_pct.png
Riesgo por segmentos de indice_vigor_ndvi_base:
| segmento | contenedores | reclamos | monto_reclamo_usd | tasa_reclamo | |
|---|---|---|---|---|---|
| 0 | (0.482, 0.53] | 3099 | 38 | 48566.0 | 0.012262 |
| 1 | (0.53, 0.614] | 2845 | 31 | 38498.0 | 0.010896 |
| 2 | (0.614, 0.682] | 2950 | 24 | 32172.0 | 0.008136 |
| 3 | (0.682, 0.744] | 2952 | 25 | 31196.0 | 0.008469 |
| 4 | (0.744, 0.857] | 2890 | 36 | 44827.0 | 0.012457 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/08_riesgo_segmentos_indice_vigor_ndvi_base.png
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/09_boxplot_materia_seca_pct_por_reclamo.png
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/09_boxplot_desviacion_materia_seca_pct_por_reclamo.png
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/09_boxplot_firmeza_pulpa_lb_por_reclamo.png
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/09_boxplot_dias_cosecha_a_packing_por_reclamo.png
Lectura: Estos gráficos no prueban causalidad por sí solos, pero muestran asociaciones compatibles con hipótesis fisiológicas y operacionales.
7. Integración HAB — Capa explicativa de calidad¶
Las tablas HAB permiten conectar variables operacionales con parámetros, defectos, causas, etapas y protocolos. Esto no reemplaza al auditoria estadístico, pero mejora la trazabilidad causal para la defensa.
# ======================================================================================
# 7. CAPA HAB — PARÁMETRO / DEFECTO / CAUSA / PROTOCOLO
# ======================================================================================
print_step("CAPA HAB — EXPLICACIÓN CAUSAL DE CALIDAD")
hab_parametro = data.get("hab_parametro", pd.DataFrame())
hab_defecto = data.get("hab_defecto", pd.DataFrame())
hab_causa = data.get("hab_causa_defecto", pd.DataFrame())
hab_protocolo = data.get("hab_protocolo", pd.DataFrame())
hab_etapa = data.get("hab_etapa", pd.DataFrame())
hab_principio = data.get("hab_principio", pd.DataFrame())
hab_summary = []
for name, df in [
("hab_parametro", hab_parametro), ("hab_defecto", hab_defecto), ("hab_causa_defecto", hab_causa),
("hab_protocolo", hab_protocolo), ("hab_etapa", hab_etapa), ("hab_principio", hab_principio)
]:
hab_summary.append({"tabla": name, "filas": df.shape[0], "columnas": df.shape[1], "columnas_lista": ", ".join(df.columns[:10]) if not df.empty else ""})
hab_summary_df = pd.DataFrame(hab_summary)
display(hab_summary_df)
hab_summary_df.to_csv(OUTPUT_DIR / "resumen_tablas_HAB_v40.csv", index=False)
# Mapeo explicativo manual variable -> capa HAB.
hab_variable_map = pd.DataFrame([
{"variable": "materia_seca_pct", "parametro_HAB": "Madurez / materia seca", "defecto_asociado": "Maduración desuniforme", "causa_probable": "Cosecha con madurez funcional desigual", "etapa": "Cosecha / packing", "protocolo": "Validar materia seca por lote y segregar por rangos", "principio": "Gestión de madurez"},
{"variable": "desviacion_materia_seca_pct", "parametro_HAB": "Uniformidad de madurez", "defecto_asociado": "Checkerboard ripening", "causa_probable": "Mezcla de fruta con distinta madurez", "etapa": "Cosecha / despacho", "protocolo": "No mezclar lotes con alta desviación", "principio": "Trazabilidad y segregación"},
{"variable": "firmeza_pulpa_lb", "parametro_HAB": "Firmeza", "defecto_asociado": "Daño mecánico / ablandamiento", "causa_probable": "Fruta con menor resistencia estructural", "etapa": "Packing / tránsito", "protocolo": "Ajustar manejo y destino según firmeza", "principio": "Control de condición"},
{"variable": "quiebre_cadena_frio_h", "parametro_HAB": "Temperatura", "defecto_asociado": "Maduración acelerada / deshidratación", "causa_probable": "Exposición fuera de rango", "etapa": "Frío / transporte", "protocolo": "Monitorear data logger y corregir excursiones", "principio": "Control térmico"},
{"variable": "tiempo_preenfriado_h", "parametro_HAB": "Remoción calor de campo", "defecto_asociado": "Senescencia acelerada", "causa_probable": "Prefrío tardío", "etapa": "Postcosecha", "protocolo": "Preenfriar dentro de ventana operativa", "principio": "Enfriamiento oportuno"},
{"variable": "transito_real_dias", "parametro_HAB": "Tiempo de tránsito", "defecto_asociado": "Pérdida de vida útil", "causa_probable": "Exposición prolongada", "etapa": "Logística", "protocolo": "Adecuar destino según vida útil", "principio": "Planificación logística"},
{"variable": "uniformidad_riego_pct", "parametro_HAB": "Uniformidad productiva", "defecto_asociado": "Heterogeneidad de madurez", "causa_probable": "Variabilidad hídrica", "etapa": "Precosecha", "protocolo": "Monitoreo y corrección de riego", "principio": "Gestión agronómica"},
{"variable": "subzona_agroclimatica", "parametro_HAB": "Zona agroclimática", "defecto_asociado": "Sensibilidad diferencial", "causa_probable": "Condición metabólica por origen", "etapa": "Origen", "protocolo": "Asignar destino por comportamiento histórico de zona", "principio": "Segmentación por origen"},
])
hab_variable_map["existe_en_base"] = hab_variable_map["variable"].isin(base_enriq.columns)
display(hab_variable_map)
hab_variable_map.to_csv(OUTPUT_DIR / "mapa_variables_HAB_v40.csv", index=False)
plt.figure(figsize=(9,4))
hab_variable_map["etapa"].value_counts().plot(kind="bar")
plt.title("Variables críticas mapeadas por etapa HAB")
plt.xlabel("Etapa")
plt.ylabel("Cantidad de variables")
savefig("10_variables_criticas_por_etapa_HAB.png")
print("Lectura:")
print("La capa HAB permite transformar variables estadísticas en hipótesis de calidad: parámetro, defecto, causa, etapa y protocolo recomendado.")
==================================================================================================== CAPA HAB — EXPLICACIÓN CAUSAL DE CALIDAD ====================================================================================================
| tabla | filas | columnas | columnas_lista | |
|---|---|---|---|---|
| 0 | hab_parametro | 18 | 12 | parametro_id, parametro_negocio, parametro_man... |
| 1 | hab_defecto | 12 | 8 | defecto_id, defecto_manual, defecto_negocio, t... |
| 2 | hab_causa_defecto | 22 | 8 | relacion_id, protocolo_id, parametro_id, defec... |
| 3 | hab_protocolo | 18 | 9 | protocolo_id, etapa_id, protocolo, descripcion... |
| 4 | hab_etapa | 9 | 8 | etapa_id, orden_cadena, etapa_manual, etapa_ne... |
| 5 | hab_principio | 8 | 6 | principio_id, principio, descripcion, aplica_e... |
| variable | parametro_HAB | defecto_asociado | causa_probable | etapa | protocolo | principio | existe_en_base | |
|---|---|---|---|---|---|---|---|---|
| 0 | materia_seca_pct | Madurez / materia seca | Maduración desuniforme | Cosecha con madurez funcional desigual | Cosecha / packing | Validar materia seca por lote y segregar por r... | Gestión de madurez | True |
| 1 | desviacion_materia_seca_pct | Uniformidad de madurez | Checkerboard ripening | Mezcla de fruta con distinta madurez | Cosecha / despacho | No mezclar lotes con alta desviación | Trazabilidad y segregación | True |
| 2 | firmeza_pulpa_lb | Firmeza | Daño mecánico / ablandamiento | Fruta con menor resistencia estructural | Packing / tránsito | Ajustar manejo y destino según firmeza | Control de condición | True |
| 3 | quiebre_cadena_frio_h | Temperatura | Maduración acelerada / deshidratación | Exposición fuera de rango | Frío / transporte | Monitorear data logger y corregir excursiones | Control térmico | True |
| 4 | tiempo_preenfriado_h | Remoción calor de campo | Senescencia acelerada | Prefrío tardío | Postcosecha | Preenfriar dentro de ventana operativa | Enfriamiento oportuno | True |
| 5 | transito_real_dias | Tiempo de tránsito | Pérdida de vida útil | Exposición prolongada | Logística | Adecuar destino según vida útil | Planificación logística | False |
| 6 | uniformidad_riego_pct | Uniformidad productiva | Heterogeneidad de madurez | Variabilidad hídrica | Precosecha | Monitoreo y corrección de riego | Gestión agronómica | True |
| 7 | subzona_agroclimatica | Zona agroclimática | Sensibilidad diferencial | Condición metabólica por origen | Origen | Asignar destino por comportamiento histórico d... | Segmentación por origen | True |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/10_variables_criticas_por_etapa_HAB.png
Lectura: La capa HAB permite transformar variables estadísticas en hipótesis de calidad: parámetro, defecto, causa, etapa y protocolo recomendado.
8. Punto 3 — Auditoría económica sin modelo¶
Esta sección cuantifica el problema actual antes de aplicar modelos: cuánto cuesta, dónde se concentra y qué segmentos explican la mayor parte del monto reclamado.
# ======================================================================================
# 8. AUDITORÍA ECONÓMICA SIN MODELO
# ======================================================================================
print_step("PUNTO 3 — AUDITORÍA ECONÓMICA SIN MODELO")
econ_df = analysis_df.copy()
if "monto_reclamo_usd" not in econ_df.columns:
econ_df["monto_reclamo_usd"] = 0.0
econ_df["monto_reclamo_usd"] = pd.to_numeric(econ_df["monto_reclamo_usd"], errors="coerce").fillna(0)
total_contenedores = len(econ_df)
total_reclamos = int(econ_df[TARGET].sum())
tasa_reclamo = total_reclamos / max(total_contenedores, 1)
monto_total = econ_df["monto_reclamo_usd"].sum()
reclamos_con_monto = econ_df.loc[econ_df["monto_reclamo_usd"] > 0, "monto_reclamo_usd"]
summary_econ = pd.DataFrame([{
"contenedores_evaluados": total_contenedores,
"reclamos_reales": total_reclamos,
"tasa_reclamo": tasa_reclamo,
"monto_total_reclamos_usd": monto_total,
"monto_promedio_reclamo_usd": reclamos_con_monto.mean() if len(reclamos_con_monto) else 0,
"monto_mediana_reclamo_usd": reclamos_con_monto.median() if len(reclamos_con_monto) else 0,
"monto_p90_reclamo_usd": reclamos_con_monto.quantile(0.90) if len(reclamos_con_monto) else 0,
"monto_max_reclamo_usd": reclamos_con_monto.max() if len(reclamos_con_monto) else 0,
}])
display(summary_econ)
summary_econ.to_csv(OUTPUT_DIR / "resumen_economico_sin_modelo_v40.csv", index=False)
print(f"Contenedores/lotes evaluados: {total_contenedores:,}")
print(f"Reclamos reales: {total_reclamos:,}")
print(f"Tasa de reclamo: {tasa_reclamo:.2%}")
print(f"Monto total reclamado USD: {monto_total:,.0f}")
# Distribución de montos.
if len(reclamos_con_monto) > 0:
plt.figure(figsize=(8,4))
plt.hist(reclamos_con_monto, bins=30)
plt.title("Distribución de monto_reclamo_usd")
plt.xlabel("Monto reclamo USD")
plt.ylabel("Frecuencia")
savefig("11_distribucion_monto_reclamo_usd.png")
else:
print("[INFO] No hay montos de reclamo > 0 en la base auditada.")
==================================================================================================== PUNTO 3 — AUDITORÍA ECONÓMICA SIN MODELO ====================================================================================================
| contenedores_evaluados | reclamos_reales | tasa_reclamo | monto_total_reclamos_usd | monto_promedio_reclamo_usd | monto_mediana_reclamo_usd | monto_p90_reclamo_usd | monto_max_reclamo_usd | |
|---|---|---|---|---|---|---|---|---|
| 0 | 14736 | 154 | 0.010451 | 195259.0 | 1267.915584 | 1298.5 | 1775.7 | 2468.0 |
Contenedores/lotes evaluados: 14,736 Reclamos reales: 154 Tasa de reclamo: 1.05% Monto total reclamado USD: 195,259 [GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/11_distribucion_monto_reclamo_usd.png
# ======================================================================================
# 8B. PARETO ECONÓMICO Y RIESGO POR SEGMENTO
# ======================================================================================
print_step("PARETO ECONÓMICO Y RIESGO POR SEGMENTO")
# Función económica por segmento.
def economic_by_segment(df, col, min_n=1, top=20):
if col not in df.columns:
return pd.DataFrame()
tbl = (
df.groupby(col, dropna=False)
.agg(
contenedores=(TARGET, "count"),
reclamos=(TARGET, "sum"),
monto_total_usd=("monto_reclamo_usd", "sum"),
monto_promedio_usd=("monto_reclamo_usd", "mean")
)
.reset_index()
)
tbl["tasa_reclamo"] = tbl["reclamos"] / tbl["contenedores"]
tbl = tbl[tbl["contenedores"] >= min_n].sort_values("monto_total_usd", ascending=False)
return tbl.head(top)
economic_tables = {}
for seg in ["temporada", "pais_destino", "macro_mercado", "cliente_tipo", "packhouse_id", "naviera", "tipo_contenedor", "subzona_agroclimatica"]:
tbl = economic_by_segment(econ_df, seg, min_n=1, top=20)
if not tbl.empty:
economic_tables[seg] = tbl
print(f"\nImpacto económico por {seg}:")
display(tbl)
tbl.to_csv(OUTPUT_DIR / f"impacto_economico_por_{seg}_v40.csv", index=False)
plot_tbl = tbl.head(10).sort_values("monto_total_usd")
plt.figure(figsize=(10, max(4, len(plot_tbl)*0.35)))
plt.barh(plot_tbl[seg].astype(str), plot_tbl["monto_total_usd"])
plt.title(f"Top impacto económico por {seg}")
plt.xlabel("Monto total reclamo USD")
plt.ylabel(seg)
savefig(f"12_impacto_economico_por_{seg}.png")
# Pareto económico por cliente_tipo o país destino, según disponibilidad.
pareto_col = "cliente_tipo" if "cliente_tipo" in econ_df.columns else ("pais_destino" if "pais_destino" in econ_df.columns else None)
if pareto_col:
pareto = economic_by_segment(econ_df, pareto_col, min_n=1, top=100).sort_values("monto_total_usd", ascending=False)
pareto["monto_acumulado"] = pareto["monto_total_usd"].cumsum()
pareto["pct_acumulado"] = pareto["monto_acumulado"] / max(pareto["monto_total_usd"].sum(), 1)
pareto.to_csv(OUTPUT_DIR / f"pareto_economico_{pareto_col}_v40.csv", index=False)
plt.figure(figsize=(10,5))
x = np.arange(len(pareto.head(20)))
plt.bar(x, pareto.head(20)["monto_total_usd"])
plt.plot(x, pareto.head(20)["pct_acumulado"] * pareto.head(20)["monto_total_usd"].max(), marker="o")
plt.title(f"Pareto económico por {pareto_col}")
plt.xlabel(pareto_col)
plt.ylabel("Monto total USD")
plt.xticks(x, pareto.head(20)[pareto_col].astype(str), rotation=45, ha="right")
savefig(f"13_pareto_economico_{pareto_col}.png")
print("Lectura:")
print("El Pareto económico permite responder dónde está el mayor impacto económico actual sin ningún modelo predictivo.")
==================================================================================================== PARETO ECONÓMICO Y RIESGO POR SEGMENTO ==================================================================================================== Impacto económico por temporada:
| temporada | contenedores | reclamos | monto_total_usd | monto_promedio_usd | tasa_reclamo | |
|---|---|---|---|---|---|---|
| 2 | 2020_2021 | 2157 | 28 | 34319.0 | 15.910524 | 0.012981 |
| 6 | 2024_2025 | 2114 | 29 | 32617.0 | 15.429044 | 0.013718 |
| 5 | 2023_2024 | 2093 | 24 | 31353.0 | 14.979933 | 0.011467 |
| 1 | 2019_2020 | 2046 | 21 | 30357.0 | 14.837243 | 0.010264 |
| 0 | 2018_2019 | 2108 | 21 | 24394.0 | 11.572106 | 0.009962 |
| 4 | 2022_2023 | 2084 | 16 | 21306.0 | 10.223608 | 0.007678 |
| 3 | 2021_2022 | 2134 | 15 | 20913.0 | 9.799906 | 0.007029 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/12_impacto_economico_por_temporada.png
Impacto económico por macro_mercado:
| macro_mercado | contenedores | reclamos | monto_total_usd | monto_promedio_usd | tasa_reclamo | |
|---|---|---|---|---|---|---|
| 1 | EU | 9743 | 148 | 189320.0 | 19.431387 | 0.015190 |
| 0 | DOM | 2655 | 5 | 4828.0 | 1.818456 | 0.001883 |
| 2 | LATAM | 715 | 1 | 1111.0 | 1.553846 | 0.001399 |
| 3 | NA | 1623 | 0 | 0.0 | 0.000000 | 0.000000 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/12_impacto_economico_por_macro_mercado.png
Impacto económico por packhouse_id:
| packhouse_id | contenedores | reclamos | monto_total_usd | monto_promedio_usd | tasa_reclamo | |
|---|---|---|---|---|---|---|
| 3 | PK_Secano | 7408 | 72 | 92750.0 | 12.520248 | 0.009719 |
| 2 | PK_Rapel_Norte | 2419 | 29 | 35481.0 | 14.667631 | 0.011988 |
| 1 | PK_Rapel_Centro | 2454 | 27 | 34078.0 | 13.886716 | 0.011002 |
| 0 | PK_Peumo | 2455 | 26 | 32950.0 | 13.421589 | 0.010591 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/12_impacto_economico_por_packhouse_id.png
Impacto económico por naviera:
| naviera | contenedores | reclamos | monto_total_usd | monto_promedio_usd | tasa_reclamo | |
|---|---|---|---|---|---|---|
| 1 | Hapag-Lloyd | 2365 | 36 | 42561.0 | 17.996195 | 0.015222 |
| 0 | CMA CGM | 2411 | 32 | 41735.0 | 17.310245 | 0.013273 |
| 2 | MSC | 2514 | 32 | 41468.0 | 16.494829 | 0.012729 |
| 3 | Maersk | 2434 | 26 | 34095.0 | 14.007806 | 0.010682 |
| 5 | ONE | 2357 | 23 | 30572.0 | 12.970725 | 0.009758 |
| 4 | N/A | 2655 | 5 | 4828.0 | 1.818456 | 0.001883 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/12_impacto_economico_por_naviera.png
Impacto económico por tipo_contenedor:
| tipo_contenedor | contenedores | reclamos | monto_total_usd | monto_promedio_usd | tasa_reclamo | |
|---|---|---|---|---|---|---|
| 2 | reefer_40 | 10675 | 130 | 162780.0 | 15.248712 | 0.012178 |
| 1 | reefer_20 | 1406 | 19 | 27651.0 | 19.666430 | 0.013514 |
| 0 | camion_frio | 2655 | 5 | 4828.0 | 1.818456 | 0.001883 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/12_impacto_economico_por_tipo_contenedor.png
Impacto económico por subzona_agroclimatica:
| subzona_agroclimatica | contenedores | reclamos | monto_total_usd | monto_promedio_usd | tasa_reclamo | |
|---|---|---|---|---|---|---|
| 3 | Valle Interior | 4909 | 53 | 67028.0 | 13.654105 | 0.010796 |
| 2 | Secano Interior | 4902 | 49 | 65636.0 | 13.389637 | 0.009996 |
| 0 | Lago Rapel | 2419 | 29 | 35481.0 | 14.667631 | 0.011988 |
| 1 | Secano Costero | 2506 | 23 | 27114.0 | 10.819633 | 0.009178 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/12_impacto_economico_por_subzona_agroclimatica.png
Lectura: El Pareto económico permite responder dónde está el mayor impacto económico actual sin ningún modelo predictivo.
9. Matrices ejecutivas¶
Se generan matrices que cruzan nivel de variable, área, disponibilidad y uso recomendado. Estas matrices son útiles para slides de defensa.
# ======================================================================================
# 9. MATRICES EJECUTIVAS PARA DEFENSA
# ======================================================================================
print_step("MATRICES EJECUTIVAS")
# Matriz nivel x área con disponibilidad.
mat_avail = audit_variables.groupby(["nombre_nivel", "area", "existe_en_base"]).size().reset_index(name="cantidad")
display(mat_avail)
mat_avail.to_csv(OUTPUT_DIR / "matriz_nivel_area_disponibilidad_v40.csv", index=False)
# Matriz resumida de nivel.
level_summary = (
audit_variables.groupby(["nivel", "nombre_nivel"])
.agg(
variables=("variable", "count"),
disponibles=("existe_en_base", "sum"),
areas=("area", lambda x: ", ".join(sorted(set(x))))
)
.reset_index()
)
level_summary["pct_disponibles"] = level_summary["disponibles"] / level_summary["variables"]
display(level_summary)
level_summary.to_csv(OUTPUT_DIR / "resumen_niveles_variables_v40.csv", index=False)
plt.figure(figsize=(10,4))
plt.bar(level_summary["nombre_nivel"], level_summary["variables"], label="Propuestas")
plt.bar(level_summary["nombre_nivel"], level_summary["disponibles"], label="Disponibles")
plt.title("Variables propuestas vs disponibles por nivel")
plt.ylabel("Cantidad de variables")
plt.xticks(rotation=35, ha="right")
plt.legend()
savefig("14_variables_propuestas_disponibles_nivel.png")
print("Lectura:")
print("Esta matriz permite explicar qué nivel de variables está disponible hoy y qué variables deben capturarse mejor para modelos causales futuros.")
==================================================================================================== MATRICES EJECUTIVAS ====================================================================================================
| nombre_nivel | area | existe_en_base | cantidad | |
|---|---|---|---|---|
| 0 | Variables Causales Directas | Frío | True | 3 |
| 1 | Variables Causales Directas | Logística | False | 1 |
| 2 | Variables Causales Directas | Madurez | True | 3 |
| 3 | Variables Causales Directas | Origen | True | 3 |
| 4 | Variables Explicativas Primarias | Clima | True | 5 |
| 5 | Variables Explicativas Primarias | Contenedor | True | 1 |
| 6 | Variables Explicativas Primarias | Cosecha | False | 2 |
| 7 | Variables Explicativas Primarias | Cosecha | True | 1 |
| 8 | Variables Explicativas Primarias | Logística | False | 2 |
| 9 | Variables Explicativas Primarias | Madurez | True | 3 |
| 10 | Variables Explicativas Primarias | Origen | True | 5 |
| 11 | Variables Explicativas Primarias | Packing | True | 1 |
| 12 | Variables de Causalidad Expandida e Interacciones | Interacciones | False | 5 |
| 13 | Variables de Entorno Operacional | Cliente | True | 1 |
| 14 | Variables de Entorno Operacional | Contenedor | True | 1 |
| 15 | Variables de Entorno Operacional | Logística | True | 2 |
| 16 | Variables de Entorno Operacional | Mercado | True | 3 |
| 17 | Variables de Entorno Operacional | Packing | True | 3 |
| 18 | Variables de Entorno Operacional | Temporal | False | 2 |
| 19 | Variables de Entorno Operacional | Temporal | True | 1 |
| 20 | Variables de Riesgo Histórico y Comercial Acum... | Históricos | False | 5 |
| 21 | Variables de Riesgo Histórico y Comercial Acum... | Históricos | True | 3 |
| nivel | nombre_nivel | variables | disponibles | areas | pct_disponibles | |
|---|---|---|---|---|---|---|
| 0 | Nivel 1 | Variables Causales Directas | 10 | 9 | Frío, Logística, Madurez, Origen | 0.900000 |
| 1 | Nivel 2 | Variables Explicativas Primarias | 20 | 16 | Clima, Contenedor, Cosecha, Logística, Madurez... | 0.800000 |
| 2 | Nivel 3 | Variables de Entorno Operacional | 13 | 11 | Cliente, Contenedor, Logística, Mercado, Packi... | 0.846154 |
| 3 | Nivel 4 | Variables de Riesgo Histórico y Comercial Acum... | 8 | 3 | Históricos | 0.375000 |
| 4 | Nivel 5 | Variables de Causalidad Expandida e Interacciones | 5 | 0 | Interacciones | 0.000000 |
[GRAFICO] Guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/graficos/14_variables_propuestas_disponibles_nivel.png
Lectura: Esta matriz permite explicar qué nivel de variables está disponible hoy y qué variables deben capturarse mejor para modelos causales futuros.
10. Respuestas automáticas para defensa¶
El notebook genera una lectura ejecutiva en lenguaje natural para responder preguntas del profesor guía o comité.
# ======================================================================================
# 10. Q&A Y CONCLUSIÓN AUTOMÁTICA
# ======================================================================================
print_step("Q&A Y CONCLUSIÓN V4.0")
# Identificar máximos de riesgo si existen tablas.
def top_sentence(table_dict, key, label):
tbl = table_dict.get(key, pd.DataFrame())
if tbl.empty:
return f"No hay información suficiente para {label}."
row = tbl.iloc[0]
return f"Mayor tasa en {label}: {row[key]} con {row['reclamos']} reclamos, {row['contenedores']} contenedores y tasa {row['tasa_reclamo']:.2%}."
qa_text = f"""
# Q&A Defensa — V4.0 Auditoría Causal y Económica
## 1. ¿Cuál es el objetivo de V4.0?
Construir una auditoría causal, operacional y económica sin entrenar modelo, para entender qué variables podrían explicar los reclamos comerciales, dónde ocurren, cuándo ocurren, con quién ocurren y cuánto cuestan actualmente.
## 2. ¿Cuál es el target oficial?
El target oficial es `reclamo_comercial`, construido exclusivamente desde `fact_inspeccion_destino`. La base auditada queda a nivel `lote_exportacion_id`.
## 3. ¿Por qué no se usa `lote_llega_mal_destino` como predictor?
Porque es una variable post-arribo. Puede usarse para auditoria descriptivo posterior, pero no para un modelo estrictamente pre-despacho.
## 4. ¿Qué tablas se incorporan?
Se incorporan tablas operacionales, tablas documentales de reclamos y tablas HAB de calidad. Las tablas documentales y HAB no son predictores: se usan para evidencia económica y explicación causal.
## 5. ¿Cuántos registros y reclamos tiene la base auditada?
Contenedores/lotes auditados: {total_contenedores:,}.
Reclamos reales: {total_reclamos:,}.
Tasa de reclamo: {tasa_reclamo:.2%}.
## 6. ¿Cuál es el impacto económico actual sin modelo?
Monto total reclamado en la base auditada: USD {monto_total:,.0f}.
Monto promedio por reclamo con monto positivo: USD {summary_econ.loc[0, 'monto_promedio_reclamo_usd']:,.0f}.
## 7. ¿Qué variables se consideran críticas?
Nivel 1: materia seca, desviación de materia seca, heterogeneidad, quiebre de frío, preenfriado, setpoint, tránsito, NDVI, riego y subzona agroclimática.
## 8. ¿Qué responde la auditoría causal?
Responde qué variables muestran asociación con reclamos, dónde ocurren, cuándo ocurren, con qué clientes/mercados/navieras/packhouses y qué condiciones de madurez, frío, clima, origen o logística presentan mayor riesgo.
## 9. ¿Qué diferencia hay entre asociación y causalidad?
La auditoría identifica asociaciones compatibles con hipótesis causales. Para afirmar causalidad se requiere un diseño causal adicional: control de confusores, definición de tratamiento, grupo control, matching o modelos causales.
## 10. ¿Qué entrega esta versión para los modelos siguientes?
Entrega un ranking de variables por nivel, área, disponibilidad, justificación fisiológica/operacional, impacto económico y capa explicativa HAB. Esto permite construir V4.1, V4.2 y V4.3 con variables mejor justificadas.
## 11. Lectura operacional principal
El problema de reclamo comercial es poco frecuente, pero económicamente relevante. La empresa necesita priorizar contenedores por riesgo antes del despacho, pero antes de modelar debe entender qué segmentos concentran reclamos y pérdidas.
## 12. Limitaciones
- No todos los reclamos tienen causa biológica; algunos pueden ser comerciales o de precio.
- La trazabilidad documental factura/NC/FF/lote puede no ser perfecta.
- Las tablas documentales no deben usarse como predictor pre-despacho.
- Las asociaciones observadas no prueban causalidad definitiva.
"""
(OUTPUT_DIR / "qa_defensa_V4_0_auditoria_causal_economica.md").write_text(qa_text, encoding="utf-8")
print(qa_text)
print("[OUTPUT] Q&A guardado:", OUTPUT_DIR / "qa_defensa_V4_0_auditoria_causal_economica.md")
print("\n" + "="*100)
print("CONCLUSIÓN FINAL — V4.0 AUDITORÍA CAUSAL Y ECONÓMICA")
print("="*100)
print(f"Base auditada: {base_enriq.shape}")
print(f"Reclamos reales: {total_reclamos}")
print(f"Tasa de reclamo: {tasa_reclamo:.2%}")
print(f"Monto total reclamos USD: {monto_total:,.0f}")
print(f"Variables estratégicas catalogadas: {len(audit_variables)}")
print("Tablas leídas OK:", load_audit_df.loc[load_audit_df['estado'].eq('OK'), 'alias'].tolist())
print("Archivos generados en:", OUTPUT_DIR)
print("="*100)
==================================================================================================== Q&A Y CONCLUSIÓN V4.0 ==================================================================================================== # Q&A Defensa — V4.0 Auditoría Causal y Económica ## 1. ¿Cuál es el objetivo de V4.0? Construir una auditoría causal, operacional y económica sin entrenar modelo, para entender qué variables podrían explicar los reclamos comerciales, dónde ocurren, cuándo ocurren, con quién ocurren y cuánto cuestan actualmente. ## 2. ¿Cuál es el target oficial? El target oficial es `reclamo_comercial`, construido exclusivamente desde `fact_inspeccion_destino`. La base auditada queda a nivel `lote_exportacion_id`. ## 3. ¿Por qué no se usa `lote_llega_mal_destino` como predictor? Porque es una variable post-arribo. Puede usarse para auditoria descriptivo posterior, pero no para un modelo estrictamente pre-despacho. ## 4. ¿Qué tablas se incorporan? Se incorporan tablas operacionales, tablas documentales de reclamos y tablas HAB de calidad. Las tablas documentales y HAB no son predictores: se usan para evidencia económica y explicación causal. ## 5. ¿Cuántos registros y reclamos tiene la base auditada? Contenedores/lotes auditados: 14,736. Reclamos reales: 154. Tasa de reclamo: 1.05%. ## 6. ¿Cuál es el impacto económico actual sin modelo? Monto total reclamado en la base auditada: USD 195,259. Monto promedio por reclamo con monto positivo: USD 1,268. ## 7. ¿Qué variables se consideran críticas? Nivel 1: materia seca, desviación de materia seca, heterogeneidad, quiebre de frío, preenfriado, setpoint, tránsito, NDVI, riego y subzona agroclimática. ## 8. ¿Qué responde la auditoría causal? Responde qué variables muestran asociación con reclamos, dónde ocurren, cuándo ocurren, con qué clientes/mercados/navieras/packhouses y qué condiciones de madurez, frío, clima, origen o logística presentan mayor riesgo. ## 9. ¿Qué diferencia hay entre asociación y causalidad? La auditoría identifica asociaciones compatibles con hipótesis causales. Para afirmar causalidad se requiere un diseño causal adicional: control de confusores, definición de tratamiento, grupo control, matching o modelos causales. ## 10. ¿Qué entrega esta versión para los modelos siguientes? Entrega un ranking de variables por nivel, área, disponibilidad, justificación fisiológica/operacional, impacto económico y capa explicativa HAB. Esto permite construir V4.1, V4.2 y V4.3 con variables mejor justificadas. ## 11. Lectura operacional principal El problema de reclamo comercial es poco frecuente, pero económicamente relevante. La empresa necesita priorizar contenedores por riesgo antes del despacho, pero antes de modelar debe entender qué segmentos concentran reclamos y pérdidas. ## 12. Limitaciones - No todos los reclamos tienen causa biológica; algunos pueden ser comerciales o de precio. - La trazabilidad documental factura/NC/FF/lote puede no ser perfecta. - Las tablas documentales no deben usarse como predictor pre-despacho. - Las asociaciones observadas no prueban causalidad definitiva. [OUTPUT] Q&A guardado: /content/capstone_outputs_V4_0_auditoria_causal_economica/qa_defensa_V4_0_auditoria_causal_economica.md ==================================================================================================== CONCLUSIÓN FINAL — V4.0 AUDITORÍA CAUSAL Y ECONÓMICA ==================================================================================================== Base auditada: (14736, 140) Reclamos reales: 154 Tasa de reclamo: 1.05% Monto total reclamos USD: 195,259 Variables estratégicas catalogadas: 56 Tablas leídas OK: ['dim', 'clima', 'cosecha', 'expo', 'insp', 'reclamo_documento_cabecera', 'reclamo_factura_detalle', 'reclamo_nota_credito_detalle', 'reclamo_ff_detalle', 'hab_parametro', 'hab_defecto', 'hab_causa_defecto', 'hab_protocolo', 'hab_etapa', 'hab_principio'] Archivos generados en: /content/capstone_outputs_V4_0_auditoria_causal_economica ====================================================================================================