"""Genera 00c_VERIFICACION.txt (chequeo de completitud del linaje) y 06_otros_outputs_clientes_intel/ (SQL de las tablas de clientes_intel que NO acaban en customer_marts, para no dejar ninguna atras). """ import json import os import textwrap DEST = "/mnt/c/Users/egutierrez/Desktop/linaje_customer_marts" PROJECT = "autingo-159109" builds = json.load(open("scratchpad/intel_build.json")) lin = json.load(open("scratchpad/intel_lineage.json")) involved = set(lin["intel_involved"]) # Catalogo completo de clientes_intel (40 objetos) reconstruido: involved + leftovers conocidos. LEFTOVER = [ "_presupuesto_persona", "_veh_cluster_feat", "_veh_tec_feat", "audit_persona_divergencias", "calidad_email_snapshot", "f0_audit_keys", "fact_impacto_campana", "map_mutualista_particular", "reco_promo_personalizada", "reco_promo_segmento", "rpt_campana", "rpt_campana_lift", "rpt_campana_usuario", "rpt_impacto_persona", "seg_audiencia", "seg_vega_persona", "sf_contact_map", "tipologia_cliente_resumen", "veh_cluster", ] # Clasificacion por proposito (a donde va cada leftover). CATEGORY = { "rpt_campana": "Informe de campanas (BI / dashboards de marketing)", "rpt_campana_lift": "Informe de campanas: lift (BI / dashboards)", "rpt_campana_usuario": "Informe de campanas por usuario (BI / dashboards)", "rpt_impacto_persona": "Informe de impacto por persona (BI / dashboards)", "fact_impacto_campana": "Hechos de impacto de campana (base de los informes)", "reco_promo_personalizada": "Recomendacion de promo personalizada (activacion)", "reco_promo_segmento": "Recomendacion de promo por segmento (activacion)", "seg_audiencia": "Audiencias para activacion (probable push a Salesforce/Marketing)", "sf_contact_map": "Mapa de contactos Salesforce (sincronizacion de IDs)", "audit_persona_divergencias": "Auditoria de calidad: divergencias en resolucion de persona", "calidad_email_snapshot": "Auditoria de calidad: snapshot de emails", "f0_audit_keys": "Auditoria de claves (control interno del pipeline)", "_presupuesto_persona": "Auxiliar: presupuestos por persona (interim)", "_veh_cluster_feat": "Auxiliar: features para clustering de vehiculo (interim)", "_veh_tec_feat": "Auxiliar: features tecnicas de vehiculo (interim)", "veh_cluster": "Clustering de vehiculo (resultado; no lo usan los marts hoy)", "tipologia_cliente_resumen": "Resumen de tipologia de cliente (BI)", "map_mutualista_particular": "Vista auxiliar: mapa mutualista/particular", "seg_vega_persona": "Segmentacion VEGA por persona (contactabilidad; lee fuentes de cliente)", } SEP = "=" * 78 + "\n" def w(path, text): full = os.path.join(DEST, path) os.makedirs(os.path.dirname(full), exist_ok=True) with open(full, "w", newline="\r\n", encoding="utf-8") as f: f.write(text) # --- 06: SQL de los leftovers que tengan build capturado --- written = [] for t in LEFTOVER: b = builds.get(t) if not b: continue out = [SEP, f"OBJETO : {PROJECT}.clientes_intel.{t}\n", f"TIPO : {b['stmt']} (NO alimenta customer_marts)\n", f"ULTIMA EJECUCION CAPTURADA: {b['last_run']}\n", SEP, "\nQUE ES / A DONDE VA\n-------------------\n", textwrap.fill(CATEGORY.get(t, "(sin clasificar)"), width=78) + "\n"] if b["refs"]: out.append("\nLEE DE\n------\n") for r in b["refs"]: out.append(f" - {PROJECT}.{r}\n") out.append("\nSQL DE CONSTRUCCION (copiable)\n------------------------------\n\n") out.append(b["query"].strip() + "\n") w(f"06_otros_outputs_clientes_intel/{t}.txt", "".join(out)) written.append(t) # --- 00c: verificacion de completitud --- v = [SEP, "VERIFICACION DE COMPLETITUD DEL LINAJE\n", SEP, "\n"] v.append("PREGUNTA: todo esto acaba en customer_marts? Comprobado.\n\n") v.append("""RESPUESTA CORTA --------------- La cadena customer_marts -> fuentes esta COMPLETA (todas las referencias resueltas, 0 tablas sin identificar). PERO customer_marts NO es el unico destino: es UNO de los consumidores de la capa clientes_intel. - clientes_intel tiene 40 objetos. - 21 de ellos alimentan (directa o indirectamente) las 14 vistas de customer_marts. - 19 NO van a customer_marts: son OTRAS salidas del mismo pipeline (informes de campana, recomendaciones de promo, audiencias, auditorias, auxiliares). El unico dataset MODELADO que lee clientes_intel es customer_marts. El resto de lo que lee clientes_intel y customer_marts son consultas de BI / ad-hoc (tablas temporales _hexhash / anon...), es decir Metabase u otros lo consumen directamente. En ese sentido customer_marts SI es terminal en el modelo (aguas abajo solo hay BI). """) v.append(SEP + "1) LAS 21 TABLAS DE clientes_intel QUE SI ALIMENTAN customer_marts\n" + SEP + "\n") for t in sorted(involved): b = builds.get(t, {}) v.append(f" - {t} ({b.get('stmt','(sin job)')})\n") v.append("\n" + SEP + "2) LAS 19 TABLAS DE clientes_intel QUE NO VAN A customer_marts\n" + SEP + "\n") v.append(" (SQL de cada una en 06_otros_outputs_clientes_intel/)\n\n") for t in LEFTOVER: sql_note = "" if t in written else " [sin SQL de job capturado]" v.append(f" - {t:28s} {CATEGORY.get(t,'')}{sql_note}\n") v.append("\n" + SEP + "3) FUENTES BASE ALCANZADAS (fin del linaje)\n" + SEP + "\n") v.append(" Fuera de clientes_intel, el pipeline lee de:\n\n") for s in sorted(lin["external_sources"]): v.append(f" - {PROJECT}.{s}\n") v.append("\n" + SEP + "4) NOTAS DE COBERTURA\n" + SEP + "\n") v.append(""" - score_clv y seg_cluster_vehiculo: usadas por customer_marts pero sin CTAS reciente en el historial de jobs (son modelos ML / cargas antiguas). Su esquema esta en 02_intermedio_clientes_intel/; no hay un SQL de un solo job que las reconstruya. - El SQL de construccion se tomo del ULTIMO job exitoso de cada tabla (INFORMATION_SCHEMA.JOBS, region europe-west1, ventana 120 dias). Si una tabla se reconstruye con otra logica fuera de esa ventana, no se captura aqui. - customer_marts: 14 vistas = el dataset entero (no falta ninguna). """) w("00c_VERIFICACION.txt", "".join(v)) print(f"06_otros_outputs_clientes_intel/ -> {len(written)} archivos") print("00c_VERIFICACION.txt -> escrito") print("\nLeftovers sin SQL capturado:", [t for t in LEFTOVER if t not in written] or "ninguno")