"""Legibilidad Flesch Reading Ease de un corpus de texto. Función pura del grupo `eda`, estilo dict-no-throw: nunca lanza. Usa la librería `textstat` con import perezoso y degradación: si `textstat` no está instalada (o falla al importar), devuelve un resultado con `available=False` en lugar de propagar el error. """ def _percentile_nearest_rank(sorted_values, pct): """Percentil por nearest-rank sobre una lista ya ordenada ascendente. rank = ceil(pct/100 * n); índice 1-based recortado a [1, n]. Devuelve None si la lista está vacía. """ n = len(sorted_values) if n == 0: return None import math rank = math.ceil((pct / 100.0) * n) if rank < 1: rank = 1 if rank > n: rank = n return sorted_values[rank - 1] def compute_text_readability(texts, sample_max=500) -> dict: """Calcula la legibilidad Flesch Reading Ease de un corpus. Args: texts: lista de str. Los elementos None, no-str o vacíos (tras strip) se descartan. Se muestrean los primeros `sample_max` documentos válidos. sample_max: número máximo de documentos a puntuar (los primeros). Returns: Dict con la forma exacta:: {"available": bool, "n_scored": int, "flesch": {"mean": float|None, "p50": float|None, "min": float|None, "max": float|None}} `available` es True si `textstat` se pudo importar. La función nunca lanza: cualquier excepción global degrada a `available=False`. """ empty = { "available": False, "n_scored": 0, "flesch": {"mean": None, "p50": None, "min": None, "max": None}, } try: # Import perezoso con degradación: textstat es una dependencia opcional. try: import textstat except Exception: return { "available": False, "n_scored": 0, "flesch": {"mean": None, "p50": None, "min": None, "max": None}, } # Filtrar y muestrear documentos válidos (los primeros sample_max). docs = [] if texts is not None: try: limit = int(sample_max) except Exception: limit = 500 if limit < 0: limit = 0 for item in texts: if not isinstance(item, str): continue if item.strip() == "": continue docs.append(item) if len(docs) >= limit: break scores = [] for doc in docs: try: score = textstat.flesch_reading_ease(doc) except Exception: continue try: score = float(score) except Exception: continue scores.append(score) n_scored = len(scores) if n_scored == 0: # textstat presente pero corpus vacío / sin puntuar. return { "available": True, "n_scored": 0, "flesch": {"mean": None, "p50": None, "min": None, "max": None}, } mean_val = round(sum(scores) / n_scored, 1) sorted_scores = sorted(scores) p50_raw = _percentile_nearest_rank(sorted_scores, 50) p50_val = round(p50_raw, 1) if p50_raw is not None else None min_val = sorted_scores[0] max_val = sorted_scores[-1] return { "available": True, "n_scored": n_scored, "flesch": { "mean": mean_val, "p50": p50_val, "min": min_val, "max": max_val, }, } except Exception: return empty