---
id: column_quality_score_py_datascience
name: column_quality_score
kind: function
lang: py
domain: datascience
version: "1.0.0"
purity: pure
signature: "def column_quality_score(col: dict) -> dict"
description: "Calcula un score de calidad de datos 0-100 para un ColumnProfile del grupo eda, con desglose completeness/validity/consistency y lista de issues legibles. Funcion pura, no muta el input."
tags: [eda, data-quality, profiling, scoring, datascience]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
example: |
  from datascience import column_quality_score
  col = {"name": "precio", "inferred_type": "float", "null_pct": 0.2,
         "unique_pct": 0.4, "flags": [], "numeric": {"outlier_pct": 0.08}}
  column_quality_score(col)
  # {"score": 86.8, "completeness": 0.8, "validity": 0.92,
  #  "consistency": 1.0, "issues": ["20% nulos", "8% outliers"]}
tested: true
tests:
  - "test_clean_column_high_score"
  - "test_half_null_lowers_completeness_and_score"
  - "test_constant_column_flags_issue"
  - "test_empty_dict_does_not_crash"
  - "test_outliers_penalize_validity"
  - "test_mostly_null_flag_halves_validity"
  - "test_high_cardinality_text_flagged_as_id"
  - "test_none_values_treated_defensively"
  - "test_does_not_mutate_input"
test_file_path: "python/functions/datascience/column_quality_score_test.py"
file_path: "python/functions/datascience/column_quality_score.py"
params:
  - name: col
    desc: >
      ColumnProfile dict del grupo eda (p.ej. salida de summarize_table_duckdb).
      Se leen sus claves de forma defensiva con .get(...) y se toleran valores
      None. Claves usadas: null_pct (0-1), inferred_type, semantic_type,
      unique_pct (0-1), flags (list[str], reconoce "constant"/"mostly_null"),
      numeric ({outlier_pct: 0-1, ...}|None) y match_rate (opcional, 0-1).
output: >
  dict con score (float 0-100, redondeado a 1 decimal), completeness (0-1),
  validity (0-1), consistency (0-1) e issues (list[str] de descripciones
  legibles de los problemas detectados). score = round(100 * (0.5*completeness
  + 0.3*validity + 0.2*consistency), 1).
---

## Ejemplo

```python
from datascience import column_quality_score

# ColumnProfile de una columna numerica con 20% nulls y 8% outliers.
col = {
    "name": "precio",
    "physical_type": "DOUBLE",
    "inferred_type": "float",
    "semantic_type": "",
    "count": 800,
    "n_rows": 1000,
    "null_count": 200,
    "null_pct": 0.20,
    "distinct_count": 400,
    "unique_pct": 0.40,
    "flags": [],
    "numeric": {"outlier_pct": 0.08},
    "categorical": None,
    "datetime": None,
}

column_quality_score(col)
# {
#   "score": 86.8,
#   "completeness": 0.8,    # 1 - 0.20
#   "validity": 0.92,       # 1 - min(0.08, 0.3)
#   "consistency": 1.0,
#   "issues": ["20% nulos", "8% outliers"],
# }
```

## Cuando usarla

Cuando hayas perfilado una tabla con el grupo `eda` (p.ej.
`summarize_table_duckdb`) y necesites un numero 0-100 por columna para
ordenar/priorizar limpieza de datos, pintar semaforos de calidad en un
dashboard, o decidir que columnas descartar antes de modelar. Es la capa de
scoring sobre el ColumnProfile crudo: lee el perfil, no toca los datos.

## Notas

Funcion pura, sin I/O ni dependencias externas, no muta `col`. Lee todas las
claves con `.get(...)` y tolera que vengan en `None` (un ColumnProfile recien
salido de `summarize_table_duckdb` trae muchas claves a `None`), por lo que
nunca falla por claves ausentes — un `{}` produce un resultado bien definido.

Pesos del score: completeness 0.5, validity 0.3, consistency 0.2.

- **completeness** = `1 - null_pct` (None -> 0 nulls -> 1.0).
- **validity**: parte de 1.0 y penaliza `min(outlier_pct, 0.3)` en columnas
  numericas, `0.5 * (1 - match_rate)` si hay `semantic_type` declarado con
  `match_rate` bajo disponible, y multiplica por 0.5 si el flag `mostly_null`
  esta presente.
- **consistency**: 1.0 salvo flag `constant` (-> 0.3, columna poco informativa)
  o texto con `unique_pct > 0.9` (-> 0.6, posible id de alta cardinalidad).
