--- name: profile_database kind: pipeline lang: py domain: pipelines purity: impure version: "1.0.0" signature: "def profile_database(db_path: str, tables: list = None, sample: int = 5000, report_dir: str = \"reports\", write_report: bool = True, min_inclusion: float = 0.9) -> dict" description: "Orquestador one-shot del grupo eda a nivel de BASE: perfila TODA una base DuckDB (todas las tablas o las indicadas) componiendo profile_table por tabla, infiere las relaciones FK inter-tabla por containment y construye el join graph con diagrama Mermaid. Ensambla un DatabaseProfile (resumen por tabla + TableProfiles completos + fk_candidates + join_graph) y opcionalmente emite un report markdown DB-level + JSON sidecar. Es la composicion canonica para hazme un EDA de esta base de datos y entender su esquema relacional." tags: [eda, relations, duckdb, profiling, data-quality, pipeline, dataops] uses_functions: - profile_table_py_pipelines - infer_fk_containment_duckdb_py_datascience - build_join_graph_py_datascience - duckdb_list_tables_py_infra - render_eda_markdown_py_datascience uses_types: [] returns: [] returns_optional: false error_type: error_go_core imports: [] tested: true tests: - "profile_database_two_related_tables" - "profile_database_writes_report" test_file_path: "python/functions/pipelines/profile_database_test.py" file_path: "python/functions/pipelines/profile_database.py" params: - name: db_path desc: "Ruta al archivo DuckDB (read-only, debe existir; no se crea)." - name: tables desc: "Lista de tablas a perfilar. None (default) usa todas las del esquema main via duckdb_list_tables." - name: sample desc: "Maximo de valores no nulos muestreados por columna en el perfil de cada tabla (se pasa a profile_table). Default 5000." - name: report_dir desc: "Directorio donde escribir los reports DB-level si write_report. Default 'reports'. Se crea si no existe." - name: write_report desc: "Si True (default) escribe report markdown DB-level + JSON sidecar timestamped en report_dir; si False no toca disco y los paths del retorno son None." - name: min_inclusion desc: "Umbral minimo de inclusion (0-1) para emitir una FK candidata (se pasa a infer_fk_containment_duckdb). Default 0.9." output: "dict {status:'ok', db_profile:, report_md_path:str|None, report_json_path:str|None} o {status:'error', error:str} (dict-no-throw)." --- ## Ejemplo ```python import os import tempfile import duckdb from pipelines.profile_database import profile_database # Base DuckDB de juguete en /tmp: customers <- orders (relacionadas). db = os.path.join(tempfile.mkdtemp(), "shop.duckdb") con = duckdb.connect(db) con.execute("CREATE TABLE customers (id INTEGER, name VARCHAR, city VARCHAR)") con.execute("INSERT INTO customers VALUES (1,'Ana','Madrid'),(2,'Luis','Sevilla'),(3,'Marta','Bilbao')") con.execute("CREATE TABLE orders (order_id INTEGER, customer_id INTEGER, total DOUBLE)") con.execute("INSERT INTO orders VALUES (10,1,99.5),(11,1,12.0),(12,2,45.0),(13,3,7.25)") con.close() r = profile_database(db, write_report=False) print(r["status"], r["db_profile"]["n_tables"]) # ok 2 print([fk["from_table"]+"."+fk["from_col"]+"->"+fk["to_table"]+"."+fk["to_col"] for fk in r["db_profile"]["fk_candidates"]]) # ['orders.customer_id->customers.id'] -> FK inferida por containment print(r["db_profile"]["join_graph"]["mermaid"].splitlines()[0]) # graph LR # Con report DB-level a disco (markdown con diagrama Mermaid + JSON sidecar): r = profile_database(db, report_dir="reports") print(r["report_md_path"], r["report_json_path"]) # reports/eda_db_20260620-101500.md reports/eda_db_20260620-101500.json ``` ## Cuando usarla Cuando necesites entender una BASE de datos entera de un golpe: el perfil de todas sus tablas mas su esquema relacional (que tabla referencia a cual, con que cardinalidad) en una sola llamada. Usala al recibir una base DuckDB desconocida, para documentar un data warehouse, para descubrir el star schema (las tablas hub del join graph) o antes de escribir joins sin tener el modelo declarado. Es el escalon DB-level sobre `profile_table` (que perfila una sola tabla): aqui ademas se infieren las FK y se dibuja el diagrama de relaciones. ## Gotchas - Impura: con `write_report=True` (default) ESCRIBE dos archivos a `report_dir` (markdown DB-level + JSON sidecar). Pasa `write_report=False` para un dry-run sin tocar disco. - Las FK se infieren por CONTAINMENT, es una HEURISTICA: A->B es candidata si los valores distintos de A estan contenidos en B (>= `min_inclusion`) y B parece clave (alta unicidad en su tabla). Puede dar falsos positivos (columnas que comparten dominio sin ser FK real, p.ej. dos columnas de codigos de pais) o perder FK reales si `min_inclusion` es muy alto o los datos estan sucios. Es un punto de partida para mapear el esquema, no un DDL autoritativo. - Perfila TODAS las tablas por defecto: en bases grandes (muchas tablas o tablas muy anchas) puede TARDAR. Acota con `tables=[...]` o baja `sample`. La inferencia de FK ademas salta pares hacia tablas con mas de 200k filas (lado caro del INTERSECT); esas relaciones quedan sin evaluar. - Tolera fallos por tabla: si el perfil de una tabla concreta falla, se anota en `db_profile["errors"]` y se sigue con las demas; `n_tables` cuenta solo las perfiladas con exito. Revisa `errors` para saber que quedo fuera. - `db_path` debe existir: DuckDB read-only NO crea la base. El muestreo de cada tabla usa el sandbox read-only por defecto (sin acceso a FS/red).