---
name: profile_bq_table
kind: pipeline
lang: py
domain: pipelines
purity: impure
version: "1.2.0"
signature: "def profile_bq_table(table_fqn: str, sample_frac: float = None, max_rows: int = 0, pseudonymize_cols: list = None, run_models: bool = True, run_series: bool = False, run_llm: bool = False, project_id: str = \"\", report_dir: str = \"reports\", duckdb_path: str = \"\", keep_duckdb: bool = False, where_sql: str = \"\", select_sql: str = \"\") -> dict"
description: "EDA one-shot de una tabla o vista de BigQuery: materializa el origen COMPLETO por defecto (todas las filas; muestreo opt-in con sample_frac; seudonimizacion PII opcional, LOPDGDD/RGPD) a un DuckDB local con load_bq_table_to_duckdb y lo perfila end-to-end con profile_table del grupo de capacidad eda, emitiendo el informe AutomaticEDA (PDF A5 movil + PPTX 16:9), Markdown y JSON sidecar. Es el adaptador BigQuery que faltaba en el grupo eda, resuelto por composicion (BigQuery -> DuckDB local -> profile_table) sin duplicar la logica de perfilado ni de render. Es el hazme un EDA de esta tabla BigQuery en una sola llamada, sobre el total de filas por defecto."
tags: [eda, bigquery, launcher]
uses_functions:
  - load_bq_table_to_duckdb_py_datascience
  - profile_table_py_pipelines
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_table.py"
params:
  - name: table_fqn
    desc: "FQN de la tabla/vista BigQuery: `project.dataset.table`."
  - name: sample_frac
    desc: "None (DEFAULT) = FULL, perfila TODAS las filas del origen. Un float en (0,1) activa el muestreo opt-in (`WHERE rand() < frac`, ~frac del total)."
  - name: max_rows
    desc: "Tope duro opcional de filas (LIMIT). 0 (DEFAULT) = sin tope. Se combina con sample_frac si ambos se pasan."
  - name: pseudonymize_cols
    desc: "Columnas PII a seudonimizar (hash) antes de materializar (LOPDGDD/RGPD). Preserva nulos y cardinalidad."
  - name: run_models
    desc: "PCA/KMeans/IsolationForest/normalidad sobre numericas. Default True (informe AutomaticEDA completo)."
  - name: run_series
    desc: "Analisis de serie temporal por columna numerica. Default False."
  - name: run_llm
    desc: "1 llamada LLM sobre el perfil agregado (nunca filas crudas). Default False."
  - name: project_id
    desc: "Proyecto GCP de facturacion. Vacio = primer segmento del FQN."
  - name: report_dir
    desc: "Directorio de salida de los reports. Default 'reports' (artefacto local gitignored)."
  - name: duckdb_path
    desc: "Ruta DuckDB a usar. Vacio = temporal autogestionado."
  - name: keep_duckdb
    desc: "Si True conserva el DuckDB materializado (para el notebook Jupyter). Default False."
  - name: where_sql
    desc: "Clausula WHERE SQL (sin la palabra WHERE) aplicada al origen y a su COUNT. Pass-through a load_bq_table_to_duckdb; se combina con sample_frac via AND. Ej: `fecha <= CURRENT_DATE() AND venta_n IS NOT NULL`. Se interpola tal cual: no usar con input no confiable."
  - name: select_sql
    desc: "Expresiones del SELECT (sin la palabra SELECT); vacio (DEFAULT) = `*`. Pass-through a load_bq_table_to_duckdb. Util para castear tipos problematicos (p. ej. BIGNUMERIC->FLOAT64) antes de perfilar. Se interpola tal cual: no usar con input no confiable."
output: "dict dict-no-throw. En exito {status:'ok', table_fqn, load:{n_rows_source,n_rows_fetched,sampled,sample_frac,pseudonymized,table,streamed, where_sql?, select_sql?}, duckdb_path, report_md_path, report_json_path, aeda_pdf_path, aeda_pptx_path, aeda_manifest_path, profile}. En error {status:'error', error, stage}. where_sql/select_sql aparecen en load solo si vienen informados."
---

## Ejemplo

```python
from pipelines.profile_bq_table import profile_bq_table

# FULL por defecto: EDA sobre TODAS las filas de la vista (3,8M).
r = profile_bq_table(
    "autingo-159109.customer_marts.customer_profile",
    pseudonymize_cols=["document_number", "full_name", "email", "phone", "postal_code", "salesforce_customer_id"],
    run_models=True,
)
print(r["load"]["n_rows_fetched"], "filas perfiladas, sampled=", r["load"]["sampled"])
print(r["aeda_pdf_path"]); print(r["aeda_pptx_path"]); print(r["report_md_path"])

# Muestreo opt-in: EDA sobre ~5 % de las filas (tabla enorme / iteracion rapida).
r = profile_bq_table(
    "autingo-159109.customer_marts.customer_profile",
    sample_frac=0.05,
    pseudonymize_cols=["document_number", "full_name", "email", "phone", "postal_code", "salesforce_customer_id"],
)

# Filtrar el origen + castear una columna BIGNUMERIC antes de perfilar. where_sql y
# select_sql se pasan al loader (que ingiere por batches Arrow, RAM acotada).
r = profile_bq_table(
    "autingo-159109.data.ventas_39M",
    where_sql="fecha <= CURRENT_DATE() AND venta_n IS NOT NULL",
    select_sql="fecha, idCentro, CAST(importe_bignumeric AS FLOAT64) AS importe",
    run_models=True,
)
print(r["load"]["n_rows_fetched"], "filas, streamed=", r["load"].get("streamed"))
```

## Cuando usarla

Cuando pidan un EDA de una tabla o vista de BigQuery ("hazme un EDA de esta
tabla BigQuery"). Es el adaptador BigQuery del grupo de capacidad `eda` por
composicion: trae el origen COMPLETO (todas las filas, por defecto) a un DuckDB
local y delega todo el perfilado y render en `profile_table`, sin adaptador
BigQuery nativo ni logica de EDA duplicada. Usala como primer paso al recibir un
dataset BigQuery desconocido, antes de modelar o limpiar, o para auditar la
calidad de una vista ya productiva. Para iteracion rapida o tablas que no quepan
en RAM, pasa `sample_frac` (muestreo opt-in).

## Gotchas

- Impura: requiere ADC de BigQuery configurado (Application Default Credentials)
  para que `load_bq_table_to_duckdb` autentique contra el proyecto.
- FULL por defecto: `sample_frac=None` perfila TODAS las filas del origen. El
  loader `load_bq_table_to_duckdb` (v1.2.0) ingiere por batches Arrow ->
  DuckDB cuando `pyarrow` esta disponible, con la RAM acotada al tamano de un
  batch (una tabla de decenas de millones de filas cabe sin cargarse entera); si
  no, cae al camino DataFrame completo (todo en RAM, varios GB posibles). Para
  acotar coste/memoria pasa `sample_frac` in (0,1), `max_rows` (tope duro) o
  `where_sql` (filtra el origen). Si por limite de recursos no cabe el total,
  dilo explicito con el maximo que si se cargo.
- `where_sql` / `select_sql` (pass-through al loader) se interpolan TAL CUAL en la
  query BigQuery: NO los construyas a partir de input no confiable (inyeccion SQL).
  `select_sql` es la via para castear columnas BIGNUMERIC (Arrow decimal256, que
  DuckDB no ingiere) a FLOAT64 antes de perfilar; si no las casteas, el loader
  devuelve `{status:'error', stage:'stream_schema'|'stream_insert'}`.
- Seudonimiza PII con `pseudonymize_cols` para cumplir LOPDGDD/RGPD ANTES de
  escribir a disco: nombres, DNI/NIE, email, telefono, direccion, IDs de cliente,
  etc. Se hashean preservando nulos y cardinalidad. Sin seudonimizar, la muestra
  materializada (DuckDB + reports) contiene datos personales reales [POL-MMNSEG-001-1.0].
- El DuckDB temporal se borra al terminar salvo `keep_duckdb=True` (pasalo para
  seguir explorando la muestra desde un notebook Jupyter). Si pasas `duckdb_path`
  explicito, la ruta se respeta y solo se conserva con `keep_duckdb=True`.
- Escribe reports a `report_dir` (default 'reports', artefacto local gitignored):
  Markdown + JSON sidecar + PDF A5 movil + PPTX 16:9 del informe AutomaticEDA.
- `run_llm=True` gasta tokens (haiku) pero solo envia el perfil agregado, nunca
  filas crudas ni datos personales.

## Capability growth log

- v1.2.0 (2026-07-02) — Añade `where_sql` y `select_sql` como pass-through al loader `load_bq_table_to_duckdb`: filtran/proyectan el origen antes de perfilar (`where_sql` tambien acota el COUNT del origen; `select_sql` permite castear BIGNUMERIC->FLOAT64). Ambos se reflejan en el bloque `load` del retorno (solo si vienen informados), junto con la nueva clave `streamed`. Hereda del loader v1.2.0 la ingesta streaming Arrow -> DuckDB por batches (RAM acotada) para tablas que no caben en RAM, con fallback DataFrame completo.
- v1.1.0 (2026-07-01) — FULL pasa a ser el DEFAULT del pipeline: se sustituye `max_rows=300000, sample=True` por `sample_frac=None` (None = perfila todas las filas) + `max_rows=0` (tope duro opcional). El muestreo es opt-in explicito (`sample_frac`). Alinea con la preferencia estandar del usuario: los EDA se corren sobre el total salvo que se pida lo contrario. Hereda el fetch acelerado (Arrow/bqstorage) de `load_bq_table_to_duckdb` v1.1.0.
