2ebc9efeb2
- 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>
314 lines
15 KiB
Python
314 lines
15 KiB
Python
"""Genera la carpeta de documentacion de linaje en el Escritorio de Windows.
|
|
|
|
A partir del grafo trazado (scratchpad/lineage_graph.json) escribe:
|
|
00_INDICE.txt resumen + mapa de capas + tabla de todos los objetos
|
|
01_marts/<vista>.txt una por vista de customer_marts: que es + arbol de linaje + SQL
|
|
02_intermedio_clientes_intel/*.txt tablas base del pipeline de inteligencia de clientes
|
|
03_producto/*.txt cadena de catalogo de producto (vistas con SQL + bases)
|
|
04_fuentes/*.txt tablas fuente (replica Postgres, Navision, imagenes, tasas)
|
|
|
|
Todos los .txt se escriben con CRLF para abrirse limpios en Bloc de notas de Windows.
|
|
"""
|
|
import json
|
|
import os
|
|
import textwrap
|
|
|
|
DEST = "/mnt/c/Users/egutierrez/Desktop/linaje_customer_marts"
|
|
DATA = json.load(open("scratchpad/lineage_graph.json"))
|
|
G = DATA["graph"]
|
|
PROJECT = DATA["project"]
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Descripciones ("que es") por objeto. La SQL/DDL incluida en cada archivo es la
|
|
# fuente de verdad; estas lineas son un resumen para orientar al lector.
|
|
# ---------------------------------------------------------------------------
|
|
DESC = {
|
|
# ---- customer_marts (marts finales, grano = persona_id / cliente) ----
|
|
"customer_marts.customer_profile":
|
|
"Ficha maestra 360 del cliente: identidad + features agregadas + score CLV + segmento. Vista de perfil que consolida todo lo demas.",
|
|
"customer_marts.customer_monetary":
|
|
"Metricas monetarias del cliente (gasto total, ticket medio, recencia/frecuencia/valor). Componente M del RFM.",
|
|
"customer_marts.customer_channel":
|
|
"Canal del cliente: canal preferido transaccional, mix aurgi/motortown/web/servicio, canal de entrada (canal8) y fuentes de origen.",
|
|
"customer_marts.customer_contactability":
|
|
"Contactabilidad del cliente: disponibilidad de email/telefono y consentimientos, a partir de la dimension persona + features + segmento.",
|
|
"customer_marts.customer_category_spend":
|
|
"Gasto del cliente desglosado por categoria de producto, a partir de la tabla de hechos de transaccion.",
|
|
"customer_marts.customer_brand_affinity":
|
|
"Afinidad de marca del cliente: que marcas compra y con que peso, cruzando transacciones con el catalogo de producto (Objeto_productos).",
|
|
"customer_marts.customer_product":
|
|
"Productos comprados por el cliente (detalle de que ha adquirido) desde la tabla de hechos de transaccion.",
|
|
"customer_marts.customer_store_spend":
|
|
"Gasto del cliente por centro/tienda desde la tabla de hechos de transaccion.",
|
|
"customer_marts.customer_temporal":
|
|
"Patrones temporales de compra del cliente (estacionalidad, recencia, frecuencia) desde transacciones + features.",
|
|
"customer_marts.customer_vehicles":
|
|
"Vehiculos asociados al cliente: dimension vehiculo + features de vehiculo + mapping N:N persona-vehiculo.",
|
|
"customer_marts.customer_payment_method":
|
|
"Metodo de pago del cliente reconstruido desde los pedidos TPV (orders/invoice/payment/payment_types).",
|
|
"customer_marts.customer_promo_usage":
|
|
"Uso de promociones/descuentos por el cliente (pedidos con descuento) desde transacciones + pedidos TPV + segmento.",
|
|
"customer_marts.customer_promo_tolerance":
|
|
"Tolerancia del cliente a promociones: respuesta a campanas + sensibilidad a descuentos en pedidos.",
|
|
"customer_marts.customer_predictive":
|
|
"Senales predictivas del cliente: score CLV, proxima mejor accion (recomendaciones) y segmento.",
|
|
|
|
# ---- clientes_intel (capa intermedia; tablas base del pipeline de inteligencia de clientes) ----
|
|
"clientes_intel.dim_persona":
|
|
"Dimension PERSONA: identidad de cliente consolidada (una fila por persona_id). Nucleo de la doble identidad persona+vehiculo.",
|
|
"clientes_intel.dim_vehiculo":
|
|
"Dimension VEHICULO: una fila por vehiculo (matricula/bastidor) con sus atributos.",
|
|
"clientes_intel.fact_transaccion":
|
|
"Tabla de HECHOS de transaccion: linea/venta por cliente. Base de casi todos los marts monetarios y de producto.",
|
|
"clientes_intel.fact_campana_respuesta":
|
|
"Tabla de HECHOS de respuesta a campanas de marketing (envio/apertura/conversion) por cliente.",
|
|
"clientes_intel.feat_cliente_persona":
|
|
"Features agregadas a nivel PERSONA (RFM, mix de canal, indicadores derivados). Alimenta perfil, monetary, channel, temporal, contactability.",
|
|
"clientes_intel.feat_cliente_vehiculo":
|
|
"Features agregadas a nivel VEHICULO. Alimenta customer_vehicles.",
|
|
"clientes_intel.seg_cliente_360":
|
|
"Segmentacion 360 del cliente (segmentos de negocio / clusters). Alimenta perfil, channel, contactability, predictive, promo_usage.",
|
|
"clientes_intel.score_clv":
|
|
"Score de valor de vida del cliente (CLV). Alimenta perfil y predictive.",
|
|
"clientes_intel.reco_acciones":
|
|
"Recomendaciones / proxima mejor accion (NBA) por cliente. Alimenta customer_predictive.",
|
|
"clientes_intel.map_persona_canal8":
|
|
"Mapeo persona -> canal8 (canal de entrada). Puente para customer_channel.",
|
|
"clientes_intel.map_persona_fuente":
|
|
"Mapeo persona -> fuente(s) de origen (de que sistema/canal proviene el cliente). Puente para customer_channel.",
|
|
"clientes_intel.map_persona_vehiculo":
|
|
"Mapeo N:N persona <-> vehiculo. Puente para customer_vehicles.",
|
|
|
|
# ---- cadena de catalogo de producto ----
|
|
"anjana_bi_datamart.Objeto_productos":
|
|
"Vista maestra de PRODUCTO: catalogo Navision + categorias CGQ + imagenes + tasa/margen por material. Se usa para afinidad de marca.",
|
|
"anjana_bi_datamart.Cruce_16_07_cgq":
|
|
"Tabla de cruce de categorias CGQ (categoria/subcategoria/tipo) usada por Objeto_productos.",
|
|
"claude_bi.productos_tasa_mat":
|
|
"Tabla de tasa/margen por material de producto. La consume Objeto_productos.",
|
|
"external_datasets.product_object_images":
|
|
"Imagenes de producto (imagen principal/secundaria). Dataset externo. La consume Objeto_productos.",
|
|
"stg_anjana_bi.producto":
|
|
"Staging de producto: cruza item de Navision con equivalencias de matriculas (SAF). Capa de preparacion sobre las tablas de SQL Server.",
|
|
|
|
# ---- fuentes base ----
|
|
"psql_dcpublic.products":
|
|
"Catalogo de productos. Replica en BigQuery de la BBDD Postgres ANJANA (DCPublic).",
|
|
"psql_dcpublic.product_categories":
|
|
"Categorias de producto. Replica Postgres ANJANA (DCPublic).",
|
|
"psql_dcpublic.product_groups":
|
|
"Grupos de producto. Replica Postgres ANJANA (DCPublic).",
|
|
"psql_dcpublic.tpv_orders_order":
|
|
"Pedidos TPV (cabecera de pedido). Replica Postgres ANJANA (DCPublic).",
|
|
"psql_dcpublic.tpv_orders_orderitem":
|
|
"Lineas de pedido TPV. Replica Postgres ANJANA (DCPublic).",
|
|
"psql_dcpublic.tpv_orders_invoice":
|
|
"Facturas TPV. Replica Postgres ANJANA (DCPublic).",
|
|
"psql_dcpublic.tpv_orders_payment":
|
|
"Pagos de pedidos TPV. Replica Postgres ANJANA (DCPublic).",
|
|
"psql_dcpublic.tpv_payment_types":
|
|
"Tipos de pago TPV (catalogo). Replica Postgres ANJANA (DCPublic).",
|
|
"mssql2022_dbo.item":
|
|
"Catalogo de articulos de Navision (SQL Server 2022, esquema dbo).",
|
|
"mssql2022_dbo.equivalencias_matriculas_saf":
|
|
"Equivalencias de matriculas (SAF) en Navision (SQL Server 2022, esquema dbo).",
|
|
}
|
|
|
|
TYPE_ES = {
|
|
"VIEW": "VISTA (tiene SQL propio)",
|
|
"MATERIALIZED VIEW": "VISTA MATERIALIZADA (tiene SQL propio)",
|
|
"BASE TABLE": "TABLA BASE (datos materializados; sin SQL de definicion, solo esquema)",
|
|
"EXTERNAL": "TABLA EXTERNA",
|
|
"UNKNOWN": "DESCONOCIDO",
|
|
}
|
|
|
|
# Carpeta destino por objeto.
|
|
def folder_of(key: str) -> str:
|
|
ds = key.split(".", 1)[0]
|
|
if ds == "customer_marts":
|
|
return "01_marts"
|
|
if ds == "clientes_intel":
|
|
return "02_intermedio_clientes_intel"
|
|
if ds in ("anjana_bi_datamart", "claude_bi", "external_datasets", "stg_anjana_bi"):
|
|
return "03_producto"
|
|
return "04_fuentes"
|
|
|
|
def fname_of(key: str) -> str:
|
|
return key.replace(".", "__") + ".txt"
|
|
|
|
def relpath_of(key: str) -> str:
|
|
return f"{folder_of(key)}/{fname_of(key)}"
|
|
|
|
def desc_of(key: str) -> str:
|
|
return DESC.get(key, "(sin descripcion)")
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Arbol de linaje recursivo (para los marts).
|
|
# ---------------------------------------------------------------------------
|
|
def render_tree(key: str, prefix: str | None = None, is_last: bool = True, seen=None) -> list[str]:
|
|
if seen is None:
|
|
seen = set()
|
|
tag = {"VIEW": "[vista]", "MATERIALIZED VIEW": "[vista mat]",
|
|
"BASE TABLE": "[TABLA BASE/FUENTE]", "EXTERNAL": "[externa]",
|
|
"UNKNOWN": "[?]"}.get(G.get(key, {"type": "UNKNOWN"})["type"], "")
|
|
|
|
if prefix is None: # raiz
|
|
lines = [f"{key} {tag}"]
|
|
child_prefix = ""
|
|
else:
|
|
connector = "└── " if is_last else "├── "
|
|
lines = [f"{prefix}{connector}{key} {tag}"]
|
|
child_prefix = prefix + (" " if is_last else "│ ")
|
|
|
|
if key in seen:
|
|
lines[-1] += " (ya expandido arriba)"
|
|
return lines
|
|
seen.add(key)
|
|
refs = G.get(key, {"refs": []}).get("refs", [])
|
|
for i, r in enumerate(refs):
|
|
lines += render_tree(r, child_prefix, i == len(refs) - 1, seen)
|
|
return lines
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Escritura.
|
|
# ---------------------------------------------------------------------------
|
|
def w(path: str, text: str):
|
|
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)
|
|
|
|
SEP = "=" * 78 + "\n"
|
|
|
|
def object_file(key: str, include_tree: bool) -> str:
|
|
node = G[key]
|
|
out = []
|
|
out.append(SEP)
|
|
out.append(f"OBJETO : {PROJECT}.{key}\n")
|
|
out.append(f"TIPO : {TYPE_ES.get(node['type'], node['type'])}\n")
|
|
out.append(f"DATASET: {key.split('.',1)[0]}\n")
|
|
out.append(SEP)
|
|
out.append("\nQUE ES\n------\n")
|
|
out.append(textwrap.fill(desc_of(key), width=78) + "\n")
|
|
|
|
if node.get("refs"):
|
|
out.append("\nDEPENDE DIRECTAMENTE DE\n-----------------------\n")
|
|
for r in node["refs"]:
|
|
out.append(f" - {PROJECT}.{r} -> ver {relpath_of(r)}\n")
|
|
|
|
if include_tree:
|
|
out.append("\nLINAJE COMPLETO (hasta la fuente)\n---------------------------------\n")
|
|
out.append("\n".join(render_tree(key)) + "\n")
|
|
|
|
out.append("\nSQL / DDL\n---------\n")
|
|
if node["type"] in ("VIEW", "MATERIALIZED VIEW"):
|
|
out.append("(Definicion de la vista. Este es el SQL que puedes copiar.)\n\n")
|
|
else:
|
|
out.append("(Tabla base: no tiene SQL de transformacion. Se incluye el CREATE TABLE\n"
|
|
" con el esquema de columnas para referencia.)\n\n")
|
|
out.append(node["ddl"].strip() + "\n")
|
|
return "".join(out)
|
|
|
|
# Marts: incluir arbol de linaje.
|
|
marts = sorted(k for k in G if k.startswith("customer_marts."))
|
|
for k in marts:
|
|
w(f"01_marts/{fname_of(k)}", object_file(k, include_tree=True))
|
|
|
|
# Resto de objetos: sin arbol (o arbol solo si es vista con dependencias).
|
|
for k in sorted(G):
|
|
if k.startswith("customer_marts."):
|
|
continue
|
|
include_tree = G[k]["type"] in ("VIEW", "MATERIALIZED VIEW") and bool(G[k].get("refs"))
|
|
w(relpath_of(k), object_file(k, include_tree=include_tree))
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# INDICE.
|
|
# ---------------------------------------------------------------------------
|
|
idx = []
|
|
idx.append(SEP)
|
|
idx.append("INDICE - LINAJE DEL DATASET customer_marts\n")
|
|
idx.append(f"Proyecto BigQuery: {PROJECT}\n")
|
|
idx.append(SEP)
|
|
idx.append("""
|
|
QUE ES ESTA CARPETA
|
|
-------------------
|
|
Documenta, para cada tabla/vista del dataset `customer_marts`, de donde salen sus
|
|
datos: la cadena completa desde el mart final hasta las tablas fuente, con el SQL
|
|
de cada vista listo para copiar y compartir.
|
|
|
|
Cada objeto tiene su propio .txt con:
|
|
- QUE ES (resumen de una linea; la SQL es la fuente de verdad)
|
|
- DE QUE DEPENDE (dependencias directas, con la ruta a su archivo)
|
|
- LINAJE COMPLETO (arbol hasta la fuente) -- solo en los marts y vistas
|
|
- SQL / DDL (el codigo: definicion de la vista, o el esquema si es tabla base)
|
|
|
|
MAPA DE CAPAS
|
|
-------------
|
|
customer_marts (VISTAS finales, grano = cliente/persona_id)
|
|
|
|
|
v
|
|
clientes_intel (TABLAS BASE: capa intermedia construida por el pipeline de
|
|
| inteligencia de clientes -- dim_*, feat_*, seg_*, score_*,
|
|
| reco_*, fact_*, map_*)
|
|
v
|
|
Fuentes:
|
|
- psql_dcpublic.* Replica en BigQuery de la BBDD Postgres ANJANA (TPV + catalogo)
|
|
- anjana_bi_datamart / claude_bi / external_datasets / stg_anjana_bi
|
|
Cadena de catalogo de PRODUCTO (Objeto_productos y sus fuentes)
|
|
- mssql2022_dbo.* Navision (SQL Server 2022, esquema dbo)
|
|
|
|
NOTA: las tablas de `clientes_intel` son TABLAS BASE: no son vistas, sino tablas que
|
|
un pipeline reconstruye cada dia con sentencias CREATE TABLE AS SELECT (CTAS). Su
|
|
esquema esta en 02_intermedio_clientes_intel/. El SQL REAL que las construye (y que
|
|
baja hasta TPV / customers / users / Navision / Salesforce) esta en la carpeta
|
|
05_construccion_clientes_intel/ -- ver tambien 00b_FUENTES_DE_CLIENTE.txt.
|
|
|
|
""")
|
|
|
|
idx.append(SEP)
|
|
idx.append("CARPETAS\n")
|
|
idx.append(SEP)
|
|
idx.append("""
|
|
01_marts/ Las 14 vistas de customer_marts (con arbol de linaje)
|
|
02_intermedio_clientes_intel/ Las 12 tablas base intermedias (esquema)
|
|
03_producto/ Cadena de catalogo de producto (vistas + bases)
|
|
04_fuentes/ Tablas fuente (replica Postgres, Navision, imagenes, tasas)
|
|
05_construccion_clientes_intel/ El SQL (CTAS) que construye cada tabla de clientes_intel
|
|
00b_FUENTES_DE_CLIENTE.txt Que consulta lee cada fuente de cliente (TPV/customers/
|
|
users/Navision/Salesforce)
|
|
|
|
""")
|
|
|
|
def index_block(title, keys):
|
|
lines = [SEP, title + "\n", SEP, "\n"]
|
|
for k in keys:
|
|
t = {"VIEW": "vista", "MATERIALIZED VIEW": "vista_mat", "BASE TABLE": "tabla",
|
|
"EXTERNAL": "externa", "UNKNOWN": "?"}.get(G[k]["type"], "")
|
|
lines.append(f"[{t:9s}] {k}\n")
|
|
lines.append(f" {desc_of(k)}\n")
|
|
lines.append(f" archivo: {relpath_of(k)}\n\n")
|
|
return "".join(lines)
|
|
|
|
idx.append(index_block("1) MARTS FINALES (customer_marts)", marts))
|
|
idx.append(index_block("2) CAPA INTERMEDIA (clientes_intel)",
|
|
sorted(k for k in G if k.startswith("clientes_intel."))))
|
|
idx.append(index_block("3) CADENA DE PRODUCTO",
|
|
sorted(k for k in G if folder_of(k) == "03_producto")))
|
|
idx.append(index_block("4) FUENTES BASE",
|
|
sorted(k for k in G if folder_of(k) == "04_fuentes")))
|
|
|
|
w("00_INDICE.txt", "".join(idx))
|
|
|
|
# Conteo final
|
|
n_files = sum(len(files) for _, _, files in os.walk(DEST))
|
|
print(f"Escrito en: {DEST}")
|
|
print(f"Archivos .txt generados: {n_files}")
|
|
print("Estructura:")
|
|
for root, dirs, files in sorted(os.walk(DEST)):
|
|
rel = os.path.relpath(root, DEST)
|
|
if rel == ".":
|
|
for f in sorted(files):
|
|
print(f" {f}")
|
|
else:
|
|
print(f" {rel}/ ({len(files)} archivos)")
|