Files
fn_registry/python/functions/datascience/build_column_dictionary.md
T
egutierrez 5a4f82cf76 chore: auto-commit (26 archivos)
- python/functions/bigquery/bq_auth.md
- python/functions/bigquery/bq_load_from_file.md
- python/functions/bigquery/bq_load_from_gcs.md
- python/functions/bigquery/client.py
- python/functions/bigquery/queries.py
- python/functions/datascience/__init__.py
- python/functions/datascience/decode_qr_image.py
- python/functions/datascience/load_bq_table_to_duckdb.md
- python/functions/datascience/load_bq_table_to_duckdb.py
- python/functions/pipelines/profile_bq_table.md
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-02 19:00:13 +02:00

6.9 KiB

id, name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, example, tested, tests, test_file_path, file_path, params, output
id name kind lang domain version purity signature description tags uses_functions uses_types returns returns_optional error_type imports example tested tests test_file_path file_path params output
build_column_dictionary_py_datascience build_column_dictionary function py datascience 1.0.0 pure def build_column_dictionary(db_profile: dict) -> dict Construye el diccionario de columnas BUSCABLE de una base entera a partir del DatabaseProfile que emite profile_database (grupo eda). Aplana db_profile['table_profiles'] (lista de TableProfile con table y columns) en una entrada por columna con tabla, tipo inferido, tipo semantico, marca de PII (RGPD/LOPDGDD), %null, cardinalidad y valores top. Responde a nivel de base 'donde esta el customer_id / telefono / IBAN'. Emite tambien pii_columns y un markdown grep-able ordenado por columna, precedido de las columnas compartidas por nombre entre tablas (candidatas a join key cross-tabla). Funcion pura, dict-no-throw, no muta el input.
eda
relations
false
from datascience import build_column_dictionary db_profile = {"table_profiles": [ {"table": "clientes", "columns": [ {"name": "email", "inferred_type": "text", "semantic_type": "email", "null_pct": 0.05, "distinct_count": 990}]}]} res = build_column_dictionary(db_profile) # res["pii_columns"] -> [{"table": "clientes", "column": "email", "is_pii": True, ...}] true
test_golden_flattens_two_tables
test_pii_flagged_from_semantic_type
test_empty_semantic_type_maps_to_none_and_not_pii
test_shared_column_names_detected_as_join_keys
test_top_values_from_categorical_block
test_empty_profile_returns_empty_ok
test_malformed_input_returns_empty_ok
test_missing_keys_read_defensively
test_does_not_mutate_input
python/functions/datascience/build_column_dictionary_test.py python/functions/datascience/build_column_dictionary.py
name desc
db_profile DatabaseProfile del grupo eda tal como lo devuelve profile_database en su clave db_profile (el dict con table_profiles). table_profiles es una lista de TableProfile; de cada uno se leen table (nombre) y columns (lista de ColumnProfile). De cada ColumnProfile se leen defensivamente con .get(...): name, inferred_type (numeric|categorical|datetime|text|boolean), semantic_type ("" que se normaliza a None; los que emite infer_semantic_type: email, iban, credit_card, phone_intl, postal_code_es, ...), null_pct (fraccion 0-1), distinct_count (cardinalidad, expuesta como n_distinct) y el bloque categorical.top (para top_values). Una entrada vacia, None o malformada produce el resultado vacio en estado ok (nunca lanza).
dict dict-no-throw con status ("ok" siempre), n_tables (int, tablas con columnas procesadas), n_columns (int total de columnas), entries (list[dict] una por columna con table, column, inferred_type, semantic_type|None, is_pii (bool), null_pct (float 0-1|None), n_distinct (int|None), top_values (list[str]|None)), pii_columns (subconjunto de entries con is_pii=True: dato personal segun [POL-MMNSEG-001-1.0]) y markdown (str, tabla grep-able ordenada por nombre de columna precedida de las columnas compartidas por nombre entre tablas). Entrada vacia o malformada -> n_tables/n_columns 0, listas vacias, markdown "".

Ejemplo

from datascience import build_column_dictionary

# db_profile minimo de juguete (forma de la clave db_profile de profile_database).
db_profile = {
    "table_profiles": [
        {
            "table": "clientes",
            "columns": [
                {"name": "customer_id", "inferred_type": "numeric",
                 "semantic_type": "", "null_pct": 0.0, "distinct_count": 1000},
                {"name": "email", "inferred_type": "text",
                 "semantic_type": "email", "null_pct": 0.05, "distinct_count": 990},
                {"name": "ciudad", "inferred_type": "categorical",
                 "semantic_type": "", "null_pct": 0.0, "distinct_count": 3,
                 "categorical": {"top": [
                     {"value": "Madrid", "count": 5, "pct": 0.5},
                     {"value": "Bilbao", "count": 3, "pct": 0.3}]}},
            ],
        },
        {
            "table": "pedidos",
            "columns": [
                {"name": "customer_id", "inferred_type": "numeric",
                 "semantic_type": "", "null_pct": 0.0, "distinct_count": 800},
                {"name": "iban", "inferred_type": "text",
                 "semantic_type": "iban", "null_pct": 0.1, "distinct_count": 795},
            ],
        },
    ]
}

res = build_column_dictionary(db_profile)
print(res["n_tables"], res["n_columns"])          # 2 5
print([(e["table"], e["column"]) for e in res["pii_columns"]])
# [('clientes', 'email'), ('pedidos', 'iban')]
print(res["markdown"])   # tabla grep-able + seccion de join keys (customer_id)

Uso real componiendo con profile_database (perfila la base y construye el diccionario):

from pipelines.profile_database import profile_database
from datascience import build_column_dictionary

r = profile_database("mi_base.duckdb", write_report=False)
if r["status"] == "ok":
    dicc = build_column_dictionary(r["db_profile"])
    # grep sobre dicc["markdown"] para localizar donde vive cada dato,
    # dicc["pii_columns"] para el inventario RGPD de la base.

Cuando usarla

Usala cuando necesites un indice tabla.columna de una base ENTERA: para localizar por busqueda "donde esta el customer_id / telefono / IBAN" antes de escribir un join, para descubrir claves de join cross-tabla (columnas con el mismo nombre en varias tablas) o para levantar un inventario de columnas con datos personales (RGPD/LOPDGDD) sobre el que auditar. Es el paso natural despues de profile_database: toma su db_profile y lo convierte en diccionario buscable.

Gotchas

  • El criterio de PII se basa SOLO en el semantic_type que hoy emite el grupo eda (infer_semantic_type): se marcan email, phone_intl, iban, credit_card y postal_code_es. El catalogo de regex NO detecta hoy nombre de persona ni DNI/NIE, asi que esas columnas caen como texto/categorico y NO se marcan automaticamente. Politica [POL-MMNSEG-001-1.0]: ante cualquier duda sobre si una columna contiene datos personales, tratala como PII y avisa antes de exponerla; pii_columns es una ayuda, no un inventario RGPD exhaustivo.
  • n_distinct se lee de la clave distinct_count del ColumnProfile (no de categorical.n_distinct); en tablas grandes puede venir de approx_unique (HyperLogLog) capado a n_rows, no exacto.
  • top_values solo se rellena si la columna trae bloque categorical (lo pone profile_table para columnas categorical/text); las numericas/datetime lo dejan en None.
  • Funcion pura: no toca disco ni muta el input. NO perfila la base — eso lo hace profile_database; aqui solo se APLANA su salida.