---
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_<dataset>.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:<DatabaseProfile con tables[resumen], table_profiles[completos], fk_candidates, join_graph{nodes,edges,mermaid,hubs}>, 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.
