Files
fn_registry/scratchpad/gen_verify.py
T
egutierrez 2ebc9efeb2 chore: auto-commit (8 archivos)
- scratchpad/gen_docs.py
- scratchpad/gen_intel.py
- scratchpad/gen_verify.py
- scratchpad/intel_build.json
- scratchpad/intel_lineage.json
- scratchpad/lineage_graph.json
- scratchpad/trace_intel.py
- scratchpad/trace_lineage.py

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-01 19:00:06 +02:00

127 lines
6.3 KiB
Python

"""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")