fix(eda): bugs de bajo riesgo del benchmark (H1,H5,H12,H13,H14) + tests faltantes

- H1: render_eda_markdown ya no aplica doble x100 a outlier_pct (336% -> real)
- H5: profile_database filtra base_tables_only (excluye VIEWs; sakila 21->16)
- H12: suggest_reexpression salta columnas no-continuas
- H13: to_returns/profile_table elige retornos (financiera) vs diferencias (fisica)
- H14: test de regresion ATTACH sqlite via information_schema
- +8 tests de las funciones eda nuevas (acf_pacf, adf_kpss, ...). 77 tests verdes
- L/M (H2,H3,H4,H6,H7,H8,H9,H10,H11) quedan en issues 0174-0177 para revision

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Egutierrez
2026-06-29 03:51:11 +02:00
parent 7ac69ab4fb
commit caf8c25d99
17 changed files with 1145 additions and 31 deletions
@@ -201,7 +201,10 @@ def render_eda_markdown(profile: dict) -> str:
if val is None:
continue
if key == "outlier_pct":
stat_rows.append([label, _fmt_pct(val)])
# outlier_pct ya viene en escala 0-100 desde describe_numeric
# (100 * n_outliers / n). NO usar _fmt_pct (multiplica x100 otra
# vez y produce porcentajes imposibles, p.ej. 7% -> 700%).
stat_rows.append([label, _fmt_num(val, 2) + "%"])
elif key == "distribution_type":
stat_rows.append([label, str(val)])
else:
@@ -373,12 +376,26 @@ def render_eda_markdown(profile: dict) -> str:
elif stl.get("note"):
rows.append(["STL", stl.get("note")])
if s.get("levels_suggested"):
rows.append(["sugerencia", "convertir a retornos (serie de niveles)"])
tr = s.get("to_returns") or {}
if tr.get("mean") is not None:
rows.append(["retorno medio (log)", _fmt_num(tr.get("mean"))])
if tr.get("std") is not None:
rows.append(["volatilidad retornos (σ)", _fmt_num(tr.get("std"))])
# La transformación recomendada depende de la semántica: retornos para
# series financieras (precio/volumen), diferencias para magnitudes
# físicas (temperatura, caudal). Aplicar "retornos" a temperatura no
# tiene sentido físico; las diferencias sí.
kind = s.get("levels_kind")
if kind == "returns":
label = "convertir a retornos (serie de niveles financiera)"
elif kind == "differences":
label = "trabajar sobre diferencias (serie de niveles no financiera)"
else:
label = "convertir a retornos o diferencias (serie de niveles)"
rows.append(["sugerencia", label])
# Las métricas de retorno (media/volatilidad) solo se muestran cuando la
# transformación recomendada son retornos; para diferencias no aplican.
if kind != "differences":
tr = s.get("to_returns") or {}
if tr.get("mean") is not None:
rows.append(["retorno medio (log)", _fmt_num(tr.get("mean"))])
if tr.get("std") is not None:
rows.append(["volatilidad retornos (σ)", _fmt_num(tr.get("std"))])
if rows:
block.append(_md_table(["aspecto", "valor"], rows))
if stat.get("warning"):