--- name: profile_bq_dataset kind: pipeline lang: py domain: pipelines purity: impure version: "1.1.0" signature: "def profile_bq_dataset(project_id: str, dataset: str, tables: list = None, include_views: bool = False, sample_frac: float = None, max_rows: int = 0, pseudonymize_cols: dict = None, report_dir: str = \"reports\", duckdb_path: str = \"\", keep_duckdb: bool = True, min_inclusion: float = 0.9, emit_pdf: bool = True, run_llm: bool = False) -> dict" description: "EDA one-shot de un dataset BigQuery ENTERO con descubrimiento cross-tabla: materializa CADA tabla del dataset (COMPLETA por defecto; muestreo opt-in con sample_frac; seudonimizacion PII por tabla, LOPDGDD/RGPD) en UN MISMO DuckDB compartido con load_bq_table_to_duckdb, lo perfila entero con profile_database (perfiles por tabla + relaciones FK inter-tabla por containment + join graph Mermaid + report markdown/JSON + PDF movil), y construye el diccionario de columnas del dataset con build_column_dictionary. Es el analogo BigQuery de profile_database a nivel de dataset, resuelto por composicion estricta (list_bq_dataset_tables -> load_bq_table_to_duckdb x N -> profile_database -> build_column_dictionary) sin duplicar descubrimiento, perfilado ni inferencia de relaciones. Es el hazme un EDA de este dataset BigQuery entero y descubre como se relacionan sus tablas, en una sola llamada." tags: [eda, bigquery, relations, launcher] uses_functions: - list_bq_dataset_tables_py_datascience - load_bq_table_to_duckdb_py_datascience - profile_database_py_pipelines - build_column_dictionary_py_datascience uses_types: [] returns: [] returns_optional: false error_type: error_go_core imports: [] tested: false tests: [] test_file_path: "" file_path: "python/functions/pipelines/profile_bq_dataset.py" params: - name: project_id desc: "Proyecto GCP (facturacion + primer segmento del FQN). Ej: 'autingo-159109'." - name: dataset desc: "Dataset BigQuery a perfilar entero. Ej: 'customer_marts'." - name: tables desc: "Lista de NOMBRES de tabla del dataset. None (DEFAULT) = todas las del dataset (filtradas por include_views)." - name: include_views desc: "Si True incluye las VIEW ademas de las BASE TABLE cuando tables=None. Default False (solo BASE TABLE, coherente con profile_database que salta las VIEWs)." - name: sample_frac desc: "None (DEFAULT) = FULL, perfila TODAS las filas de cada tabla. Un float en (0,1) activa el muestreo opt-in por tabla (`WHERE rand() < frac`)." - name: max_rows desc: "Tope duro de filas por tabla (LIMIT). 0 (DEFAULT) = sin tope. Se aplica a cada tabla materializada." - name: pseudonymize_cols desc: "Dict {\"tabla\": [\"col1\", \"col2\"]} de columnas PII a seudonimizar (hash) por tabla ANTES de materializar (LOPDGDD/RGPD [POL-MMNSEG-001-1.0]). Preserva nulos y cardinalidad." - name: report_dir desc: "Directorio de salida de los reports + del DuckDB compartido por defecto. Default 'reports' (artefacto local gitignored). Se crea si no existe." - name: duckdb_path desc: "Ruta del DuckDB compartido donde se materializan todas las tablas. Vacio (DEFAULT) = report_dir/eda_bq_.duckdb (se limpia si ya existia)." - name: keep_duckdb desc: "Si True (DEFAULT) conserva el DuckDB materializado: es el artefacto explorable post-EDA (notebook Jupyter, joins ad-hoc). Con False se borra al terminar." - name: min_inclusion desc: "Umbral de inclusion (0-1) para emitir una FK candidata cross-tabla (se pasa a profile_database -> infer_fk_containment_duckdb). Default 0.9." - name: emit_pdf desc: "Si True (DEFAULT) emite el PDF movil DB-level (resumen de tablas + relaciones FK + join graph). Se pasa a profile_database." - name: run_llm desc: "Si True (default False) activa la capa LLM interpretativa por tabla (se pasa a profile_database -> profile_table): una llamada LLM por tabla sobre el perfil agregado, nunca filas crudas." output: "dict dict-no-throw. En exito {status:'ok', project_id, dataset, n_tables_loaded, n_tables_profiled, tables_skipped:[...], errors:[...], duckdb_path, db_profile:, column_dictionary:{entries,pii_columns} (sin markdown), report_md_path, report_json_path, report_pdf_path, dict_md_path, dict_json_path}. En error {status:'error', error, stage}." --- ## Ejemplo ```python from pipelines.profile_bq_dataset import profile_bq_dataset # FULL por defecto: EDA del dataset ENTERO (todas las tablas, todas las filas), # con FK cross-tabla + join graph + diccionario de columnas. El DuckDB compartido # queda en reports/eda_bq_customer_marts.duckdb para seguir explorando. r = profile_bq_dataset("autingo-159109", "customer_marts") print(r["n_tables_loaded"], "tablas materializadas,", r["n_tables_profiled"], "perfiladas") print("FKs:", [f"{fk['from_table']}.{fk['from_col']}->{fk['to_table']}.{fk['to_col']}" for fk in r["db_profile"]["fk_candidates"]]) print(r["report_md_path"]); print(r["report_pdf_path"]); print(r["dict_md_path"]) print("DuckDB explorable:", r["duckdb_path"]) # Dataset con tablas enormes: muestreo opt-in + PII seudonimizada por tabla. r = profile_bq_dataset( "autingo-159109", "customer_marts", sample_frac=0.05, pseudonymize_cols={ "customer_profile": ["document_number", "full_name", "email", "phone"], }, ) ``` ## Cuando usarla Cuando pidan un EDA de un DATASET BigQuery entero y no solo de una tabla: quieres el perfil de todas sus tablas MAS su esquema relacional (que tabla referencia a cual, con que cardinalidad) descubierto cross-tabla en una sola llamada. Es el escalon a nivel de dataset sobre `profile_bq_table` (una tabla) y el adaptador BigQuery de `profile_database` (una base DuckDB). Usala al recibir un dataset BigQuery desconocido, para documentar un data mart, para descubrir el star schema (las tablas hub del join graph) o antes de escribir joins sin tener el modelo declarado. Para datasets con tablas enormes, pasa `sample_frac` o `max_rows` y dejalo declarado en el report. ## Gotchas - Impura: requiere ADC de BigQuery configurado (Application Default Credentials). Si el ADC del usuario lleva un quota project ajeno, `load_bq_table_to_duckdb` / `list_bq_dataset_tables` aplican `creds.with_quota_project(None)` para evitar el 403 USER_PROJECT_DENIED — remitido a los gotchas de esas funciones. - Coste de traer un DATASET entero: FULL por defecto materializa TODAS las filas de CADA tabla a RAM (via BigQuery Storage Read API/Arrow) antes de volcar al DuckDB compartido. Un dataset con varias tablas de millones de filas puede costar en tiempo, bytes escaneados de BigQuery y GBs de RAM/disco. Acota con `sample_frac` in (0,1) (muestreo opt-in por tabla) o `max_rows` (tope duro por tabla). Si por limite de recursos no cabe el total, dilo explicito en el report con el maximo que si se cargo. - El DuckDB compartido puede ocupar GBs: todas las tablas del dataset viven en un MISMO archivo (necesario para que la inferencia de FK opere cross-tabla). Con `keep_duckdb=True` (default) queda en disco como artefacto explorable; pasa `keep_duckdb=False` para borrarlo al terminar. Con `duckdb_path` explicito la ruta se respeta; el path por defecto se limpia al inicio para no mezclar tablas de corridas anteriores. - FK por containment es una HEURISTICA (falsos positivos/negativos posibles) y `profile_database` SALTA los pares de FK hacia tablas con mas de 200k filas (el lado caro del INTERSECT): esas relaciones quedan sin evaluar. Es un mapa de partida del esquema, no un DDL autoritativo. - Vistas excluidas por defecto (`include_views=False`, coherente con profile_database que salta VIEWs — perfilarlas infla n_tables y multiplica FK falsas). Pasa `include_views=True` solo si necesitas perfilarlas como si fueran tablas materializadas. - Seudonimiza PII con `pseudonymize_cols` (dict por tabla) para cumplir LOPDGDD/ RGPD [POL-MMNSEG-001-1.0] ANTES de escribir a disco: nombres, DNI/NIE, email, telefono, direccion, IDs de cliente, IBAN, etc. Sin seudonimizar, el DuckDB compartido + los reports contienen datos personales reales. - Tolera fallos por tabla: si una carga falla, se anota en `errors[]` + `tables_skipped[]` y el pipeline sigue con las demas; `n_tables_profiled` cuenta solo las perfiladas con exito. Revisa `errors` para saber que quedo fuera. - Escribe a `report_dir` (default 'reports', artefacto local gitignored): el report DB-level de `profile_database` (markdown + JSON + PDF si emit_pdf) MAS el diccionario de columnas (`..._dict.md` + `..._dict.json`). ## Capability growth log - v1.1.0 (2026-07-02) — añade `run_llm` (passthrough a profile_database -> profile_table: una llamada LLM por tabla sobre el perfil agregado). Default False, sin breaking changes.