"""Detecta la columna temporal y las columnas numericas de un TableProfile (grupo eda). Funcion pura y determinista: a partir de la lista de columnas de un TableProfile producido por el grupo de capacidad `eda` (cada elemento es un ColumnProfile dict), decide cual es la columna de orden temporal y que columnas numericas hay disponibles para graficar una serie en el tiempo. Es la pieza que usa el capitulo TIMESERIES del AutomaticEDA para decidir si la tabla admite analisis de serie temporal. Lectura 100% defensiva al estilo "dict-no-throw" del grupo eda: nunca lanza excepcion, siempre devuelve el mismo conjunto de claves. """ # semantic_type que el profiler (infer_semantic_type) emite para fechas/datetimes. _DATETIME_SEMANTICS = ("datetime_iso", "date_eu") def detect_time_column(columns: list) -> dict: """Detecta la columna temporal y las numericas de una lista de ColumnProfile. Recorre los ColumnProfile de un TableProfile y clasifica cada columna como temporal o numerica leyendo de forma defensiva sus claves. Una columna es temporal si su ``inferred_type == "datetime"`` o si su ``semantic_type`` esta en {``"datetime_iso"``, ``"date_eu"``}. La columna temporal elegida (``time_col``) es la PRIMERA temporal en el orden de la lista. Las numericas (``numeric_cols``) son las de ``inferred_type == "numeric"``, en orden. Funcion pura: no hace I/O, no muta el input, es determinista. Args: columns: lista de ColumnProfile dict del grupo eda. Cada elemento suele tener claves como ``name``, ``inferred_type``, ``semantic_type`` y ``numeric``. Los elementos que no sean dict se ignoran. Si ``columns`` es None, no es lista o esta vacia, se devuelve el dict "no aplica". Returns: Siempre un dict con las mismas claves:: { "time_col": str | None, # columna temporal elegida (None si no hay) "time_semantic": str, # semantic_type de la temporal ("" si no aplica) "numeric_cols": [str, ...], # columnas con inferred_type == "numeric" "n_datetime_cols": int, # nÂș de columnas temporales detectadas "datetime_cols": [str, ...],# todas las temporales, en orden de aparicion "reason": str, # frase corta (en espanol) que explica la eleccion } """ # Caso "no aplica": entrada invalida o vacia. if not isinstance(columns, list) or not columns: return { "time_col": None, "time_semantic": "", "numeric_cols": [], "n_datetime_cols": 0, "datetime_cols": [], "reason": "no se detecto columna de fecha/datetime", } datetime_cols: list[str] = [] datetime_semantics: list[str] = [] numeric_cols: list[str] = [] for col in columns: # Ignora elementos que no sean dict sin fallar. if not isinstance(col, dict): continue name = col.get("name") if name is None: name = "" else: name = str(name) inferred_type = col.get("inferred_type") or "" semantic_type = col.get("semantic_type") or "" is_datetime = inferred_type == "datetime" or semantic_type in _DATETIME_SEMANTICS if is_datetime: datetime_cols.append(name) datetime_semantics.append(semantic_type) if inferred_type == "numeric": numeric_cols.append(name) if not datetime_cols: return { "time_col": None, "time_semantic": "", "numeric_cols": numeric_cols, "n_datetime_cols": 0, "datetime_cols": [], "reason": "no se detecto columna de fecha/datetime", } time_col = datetime_cols[0] time_semantic = datetime_semantics[0] if len(datetime_cols) == 1: reason = f"columna temporal '{time_col}' detectada" else: reason = ( f"{len(datetime_cols)} columnas temporales; se elige la primera " f"'{time_col}'" ) return { "time_col": time_col, "time_semantic": time_semantic, "numeric_cols": numeric_cols, "n_datetime_cols": len(datetime_cols), "datetime_cols": datetime_cols, "reason": reason, }