---
name: profile_table
kind: pipeline
lang: py
domain: pipelines
purity: impure
version: "1.0.0"
signature: "def profile_table(db_path: str, table: str, sample: int = 5000, report_dir: str = \"reports\", write_report: bool = True) -> dict"
description: "Orquestador one-shot del grupo de capacidad eda: perfila UNA tabla DuckDB end-to-end componiendo las 7 funciones del grupo (perfil base SQL + muestreo read-only + inferencia semantica + promocion de tipo + estadistica numerica/categorica + score de calidad + render markdown) y emite el TableProfile completo mas (opcional) un report markdown y un JSON sidecar. Es la composicion canonica para hazme un EDA de esta tabla."
tags: [eda, duckdb, profiling, data-quality, pipeline, dataops]
uses_functions:
  - summarize_table_duckdb_py_datascience
  - describe_numeric_py_datascience
  - summarize_categorical_py_datascience
  - infer_semantic_type_py_datascience
  - column_quality_score_py_datascience
  - render_eda_markdown_py_datascience
  - duckdb_query_readonly_py_infra
uses_types: []
returns: []
returns_optional: false
error_type: error_go_core
imports: []
tested: true
tests:
  - "VARCHAR-entera se promociona a numeric con bloque numeric y key_candidates es lista"
test_file_path: "python/functions/pipelines/profile_table_test.py"
file_path: "python/functions/pipelines/profile_table.py"
params:
  - name: db_path
    desc: "Ruta al archivo DuckDB (read-only, debe existir; no se crea)."
  - name: table
    desc: "Nombre de la tabla a perfilar."
  - name: sample
    desc: "Maximo de valores no nulos muestreados por columna para el enriquecimiento (describe_numeric / summarize_categorical / infer_semantic_type). Default 5000."
  - name: report_dir
    desc: "Directorio donde escribir los reports si write_report. Default 'reports'. Se crea si no existe."
  - name: write_report
    desc: "Si True (default) escribe report markdown + JSON sidecar timestamped en report_dir; si False no toca disco y los paths del retorno son None."
output: "dict {status:'ok', profile:<TableProfile enriquecido con quality_score, key_candidates y type_breakdown recalculado>, report_md_path:str|None, report_json_path:str|None} o {status:'error', error:str} (dict-no-throw)."
---

## Ejemplo

```python
import os
from pipelines.profile_table import profile_table

# Tabla real: freelance_projects (35 filas) en la DuckDB del monitor de captacion.
db = os.path.expanduser("~/.fn_freelance/freelance.duckdb")

r = profile_table(db, "freelance_projects", sample=5000, write_report=False)
print(r["status"], r["profile"]["quality_score"], r["profile"]["type_breakdown"])
# ok 98.9 {'numeric': 1, 'categorical': 9, 'datetime': 2, 'text': 0, 'boolean': 1}
#   ^ 'bids' (VARCHAR '1'..'107') se promociono a numeric via semantic_type=integer.

# Con report a disco (markdown + JSON sidecar en reports/):
r = profile_table(db, "freelance_projects")
print(r["report_md_path"], r["report_json_path"])
# reports/eda_freelance_projects_20260620-101500.md reports/eda_freelance_projects_20260620-101500.json
```

## Cuando usarla

Cuando necesites un EDA completo de una tabla DuckDB en una sola llamada: perfil
por columna + estadistica fina + calidad + report listo para leer. Usala como
primer paso al recibir un dataset desconocido, antes de modelar o limpiar, o
para auditar la calidad de una tabla ya productiva. Reemplaza orquestar a mano
`summarize_table_duckdb` -> muestreo -> `describe_numeric`/`summarize_categorical`
-> `column_quality_score` -> `render_eda_markdown` columna por columna.

## Gotchas

- Impura: con `write_report=True` (default) ESCRIBE dos archivos a `report_dir`
  (markdown + JSON). Pasa `write_report=False` para un dry-run sin tocar disco.
- La promocion de tipo es una HEURISTICA sobre la muestra: una columna VARCHAR se
  reclasifica a `numeric` solo si su `semantic_type` es integer/decimal/currency
  y al menos el 80% de la muestra parsea a float; a `datetime` si el
  `semantic_type` es datetime_iso/date_eu. Tablas con datos sucios o muestras no
  representativas pueden quedar mal clasificadas; sube `sample` para muestras mas
  fiables (coste: mas filas traidas a RAM por columna).
- Las columnas promovidas a `datetime` aun NO reciben perfil fino:
  `col["datetime"]` queda en `None` (la funcion `profile_datetime` del grupo
  llega en otra fase). Su `semantic_type` si se conserva.
- El parseo numerico limpia simbolos de moneda (€/$/£/EUR/USD/GBP), espacios y
  separadores de miles; con coma y punto juntos asume punto=miles, coma=decimal.
  Formatos exoticos pueden descartarse silenciosamente del calculo numerico.
- `db_path` debe existir: DuckDB read-only NO crea la base. El muestreo usa el
  sandbox por defecto de `duckdb_query_readonly` (sin acceso a FS/red).
