---
id: column_quality_score_py_datascience
name: column_quality_score
kind: function
lang: py
domain: datascience
version: "2.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. Combina completeness (0.6) y validity (0.4) con renormalizacion por aplicabilidad; los outliers, columnas constantes e ids NO bajan el score (van a observations). Devuelve desglose por dimension, issues (defectos) y observations (señales analiticas). 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": "numeric", "null_pct": 0.2,
         "unique_pct": 0.4, "flags": [], "numeric": {"outlier_pct": 8.0}}
  column_quality_score(col)
  # {"score": 88.0, "completeness": 0.8, "validity": 1.0,
  #  "applicable": ["completeness", "validity"], "issues": ["20% nulos"],
  #  "observations": ["8% de valores atípicos (z-score>3): ..."]}
tested: true
tests:
  - "test_clean_column_high_score"
  - "test_weights_60_40_native_type"
  - "test_outliers_do_not_penalize_score"
  - "test_nulls_lower_score_more_than_outliers"
  - "test_validity_from_parse_rate_lowers_score"
  - "test_validity_from_match_rate"
  - "test_free_text_renormalizes_to_completeness_only"
  - "test_all_null_column_scores_zero"
  - "test_constant_column_scores_full_and_is_observation"
  - "test_high_cardinality_id_scores_full_and_is_observation"
  - "test_mostly_null_no_double_counts_validity"
  - "test_empty_dict_does_not_crash"
  - "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 /
      profile_table). Se leen sus claves de forma defensiva con .get(...) y se
      toleran valores None. Claves usadas: null_pct (0-1), n_rows, empty_count
      (texto), inferred_type, semantic_type, validity_rate (0-1, lo expone
      profile_table al promocionar texto a numero/fecha), match_rate (0-1),
      unique_pct (0-1), flags (list[str], reconoce
      "constant"/"possible_id"/"high_cardinality") y numeric ({outlier_pct: 0-100,
      skew, ...}|None).
output: >
  dict con score (float 0-100, 1 decimal), completeness (0-1), validity (0-1 o
  None si no aplicable), dimensions ({completeness, validity}), applicable
  (list[str] de dimensiones que entraron en el score), issues (list[str] SOLO de
  defectos de calidad: nulos, vacios, valores no conformes) y observations
  (list[str] de señales analiticas que NO bajan el score: outliers, columna
  constante, posible id, asimetria). score = round(100 * (0.6*completeness +
  0.4*validity) / pesos_aplicables, 1), renormalizado cuando validity no aplica.
---

## 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": "numeric",
    "semantic_type": "",
    "n_rows": 1000,
    "null_count": 200,
    "null_pct": 0.20,
    "distinct_count": 400,
    "unique_pct": 0.40,
    "flags": [],
    "numeric": {"outlier_pct": 8.0, "skew": 0.3},
    "categorical": None,
    "datetime": None,
}

column_quality_score(col)
# {
#   "score": 88.0,            # 100 * (0.6*0.8 + 0.4*1.0)
#   "completeness": 0.8,      # 1 - 0.20
#   "validity": 1.0,          # numerica nativa: el tipo es conforme
#   "dimensions": {"completeness": 0.8, "validity": 1.0},
#   "applicable": ["completeness", "validity"],
#   "issues": ["20% nulos"],                       # SOLO defectos de calidad
#   "observations": ["8% de valores atípicos (z-score>3): ..."],  # NO bajan score
# }
```

## Cuando usarla

Cuando hayas perfilado una tabla con el grupo `eda` (p.ej.
`summarize_table_duckdb` / `profile_table`) y necesites un numero 0-100 por
columna para ordenar/priorizar limpieza de datos, pintar semaforos de calidad,
o decidir que columnas descartar antes de modelar. Separa los **defectos de
calidad reales** (`issues`: nulos, vacios, valores que no parsean a su tipo) de
las **observaciones analiticas** (`observations`: outliers, columnas constantes,
ids), que se reportan pero no penalizan. Es la capa de scoring sobre el
ColumnProfile crudo: lee el perfil, no toca los datos.

## Gotchas

Funcion pura, sin I/O, no muta `col`. Aun asi conviene saber:

- **Los outliers NO bajan el score.** Un valor extremo puede ser real y correcto
  (un cliente que compra mucho); detectar atipicos es analisis de la
  distribucion, no un juicio de correccion. Salen en `observations`, no en
  `issues`. Mismo trato para columnas constantes e identificadores de alta
  cardinalidad: son observaciones, no defectos.
- **`validity` puede ser `None`** (no aplicable): texto libre sin `semantic_type`
  ni `validity_rate`, o columna 100% nula. En ese caso el score se renormaliza a
  solo `completeness` (la columna no se premia ni castiga por algo no medible).
- **`outlier_pct` se interpreta en escala 0-100** (la que emite
  `describe_numeric`, z-score>3). Pasar una fraccion 0-1 produce un texto de
  observacion con el % equivocado, pero NUNCA afecta al score.
- **`validity_rate` lo puebla `profile_table`** al promocionar una columna de
  texto a numero/fecha (fraccion que parsea). Si no esta presente y el tipo es
  nativo numerico/fecha/bool, `validity = 1.0`.
- Sin doble conteo: la falta de datos cuenta solo en `completeness` (el antiguo
  castigo de `mostly_null` sobre `validity` se elimino).

## Capability growth log

- v2.0.0 (2026-06-30) — nueva formula de calidad (report 2046): pesos 60/40
  (completeness/validity) con renormalizacion por aplicabilidad; se elimina la
  dimension `consistency`-como-informatividad y el doble castigo de
  `mostly_null`; los outliers/constantes/ids salen del score a `observations`;
  validity mide conformidad real (parse rate / match rate / tipo nativo). Salida
  ampliada con `dimensions`, `applicable` y `observations`.
- v1.0.0 — version inicial: pesos 50/30/20 (completeness/validity/consistency),
  los outliers penalizaban validity (con bug de escala) y consistency penalizaba
  informatividad.
