cab0fbf0a3
CAP4 num_distr: - Mueve el párrafo introductorio largo del histograma/boxplot al glosario (nuevo término clicable "histograma_boxplot"); el cuerpo del capítulo solo nombra el término con [[term:histograma_boxplot]] y la explicación completa (código de colores, 1,5·IQR, lectura de asimetría) vive en la entrada del glosario. La información se traslada, no se pierde. - Añade por columna numérica la descripción de negocio del LLM y la unidad, leídas de profile['llm']['dictionary'] (empareja por nombre de columna). Sin bloque LLM el bloque de descripción se omite limpiamente. CAP5 cat_distr: - Mueve el párrafo "Cada columna categórica ocupa su propia página..." al glosario (nuevo término clicable "pagina_categorica"); el intro solo nombra los términos entropía y pagina_categorica. - Añade descripción LLM + unidad por columna (misma fuente que CAP4). - Cambia el donut/pie por gráfico de barras horizontales (nueva función del registry categorical_top_bar_figure_py_datascience, contrato de entrada idéntico al donut para swap directo) más su fallback inline de barras. - Marca cada Group de columna con layout="side_by_side": en PPTX la tabla de cardinalidad queda a la izquierda y la barra a la derecha; en PDF se apila (A5 estrecho). No toca los renderers — el soporte de layout ya existía. Glosario: - Catálogo canónico _BASELINE_TERMS con las definiciones de los dos términos nuevos; build_glosario completa la definición de un término registrado sin ella desde el catálogo (los chapters solo registran clave+label). Tests actualizados (donut→barras, side_by_side, LLM desc/unidad, glosario) y nueva función con sus tests. Suite del subsistema + acceptance verde.
103 lines
5.0 KiB
Python
103 lines
5.0 KiB
Python
"""Glossary chapter (GLOSARIO) — always the last chapter, clickable terms.
|
||
|
||
Renders one entry per glossary term that the other chapters registered during
|
||
the document build through ``ctx['glossary'].add(key, label, definition)`` (see
|
||
``GlossaryCollector`` in ``model.py``). Each entry is a clickable destination:
|
||
every in-text appearance a chapter marked with ``[[term:key]]texto[[/term]]``
|
||
becomes a real jump to its entry here — PDF link annotations (PyMuPDF) and PPTX
|
||
native slide jumps, both wired by the renderers.
|
||
|
||
Returns ``None`` when no term was registered (there is nothing to show), so the
|
||
chapter simply disappears from documents that did not mark any term.
|
||
|
||
Contract: build_<id>(profile, ctx) -> Chapter | None ; CHAPTER_VERSION = "x.y.z".
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from .. import model
|
||
|
||
CHAPTER_VERSION = "1.1.0"
|
||
CHAPTER_ID = "glosario"
|
||
CHAPTER_TITLE = "Glosario"
|
||
|
||
# Canonical definitions for cross-cutting terms — the "how to read it" entries
|
||
# that do not belong to a single chapter. A chapter only needs to *register* the
|
||
# term (``ctx['glossary'].add(key, label)``) and mark its in-text appearance with
|
||
# ``[[term:key]]…[[/term]]``; this chapter supplies the full definition here when
|
||
# the collector carries the term without one. Keeping the prose in a single place
|
||
# avoids repeating a long paragraph inline in every chapter that names the term
|
||
# (the explanation moved out of the NUM DISTR and CAT DISTR intros lives here).
|
||
_BASELINE_TERMS = {
|
||
"histograma_boxplot": {
|
||
"label": "Cómo leer el histograma y el boxplot",
|
||
"definition": (
|
||
"Para cada columna numérica se muestra su histograma con tres líneas "
|
||
"de referencia: la media (línea roja discontinua), la mediana (línea "
|
||
"verde continua) y la banda ±1σ (zona sombreada que cubre una "
|
||
"desviación estándar a cada lado de la media). Debajo, alineado al "
|
||
"mismo eje horizontal, un boxplot de Tukey: la caja abarca del primer "
|
||
"al tercer cuartil (P25–P75), la línea interior es la mediana y los "
|
||
"bigotes llegan hasta 1,5·IQR; los puntos rojos señalan que hay "
|
||
"valores más allá de las vallas (posibles atípicos). Comparar la media "
|
||
"con la mediana revela la asimetría: si la media supera a la mediana la "
|
||
"cola larga cae hacia los valores altos (asimetría a la derecha), y al "
|
||
"revés hacia los bajos."),
|
||
},
|
||
"pagina_categorica": {
|
||
"label": "Cómo se organiza cada página categórica",
|
||
"definition": (
|
||
"Cada columna categórica ocupa su propia página: muestra sus métricas "
|
||
"de cardinalidad —incluida la entropía—, una nota que señala "
|
||
"cardinalidad problemática (columnas que se comportan como "
|
||
"identificador, con casi todos los valores distintos, o dominadas por "
|
||
"una sola categoría), la tabla de las categorías más frecuentes (top-k, "
|
||
"con su conteo y porcentaje) y un gráfico de barras de las categorías "
|
||
"más comunes (top-k más una barra «Otros» que agrupa la cola). El total "
|
||
"de filas del dataset se usa como referencia para interpretar los "
|
||
"conteos."),
|
||
},
|
||
}
|
||
|
||
|
||
def _resolve_term(term: dict) -> tuple:
|
||
"""Return (label, definition) for a collected term, completing a missing
|
||
definition (and, if absent, the label) from the canonical baseline catalog."""
|
||
key = model._safe_str(term.get("key"))
|
||
label = model._safe_str(term.get("label"))
|
||
definition = model._safe_str(term.get("definition"))
|
||
base = _BASELINE_TERMS.get(key)
|
||
if base:
|
||
if not definition.strip():
|
||
definition = model._safe_str(base.get("definition"))
|
||
if not label.strip() or label == key:
|
||
label = model._safe_str(base.get("label")) or label
|
||
return label, definition
|
||
|
||
|
||
def build_glosario(profile: dict, ctx: dict):
|
||
"""Build the glossary Chapter from the shared collector, or None if empty."""
|
||
ctx = ctx or {}
|
||
glossary = ctx.get("glossary")
|
||
if not isinstance(glossary, model.GlossaryCollector) or not glossary:
|
||
return None
|
||
|
||
blocks = [
|
||
model.Heading(text="Glosario de términos", level=1),
|
||
model.Markdown(text=(
|
||
"Definición de los términos técnicos que aparecen en el informe. "
|
||
"Cada término va resaltado en el texto y, al pulsarlo, salta a su "
|
||
"definición en esta sección.")),
|
||
]
|
||
# One clickable destination per term, alphabetically by visible label. A term
|
||
# registered without a definition is completed from the canonical baseline.
|
||
for term in glossary.terms(by="label"):
|
||
label, definition = _resolve_term(term)
|
||
blocks.append(model.GlossaryEntry(
|
||
key=model._safe_str(term.get("key")),
|
||
label=label,
|
||
definition=definition))
|
||
|
||
return model.Chapter(id=CHAPTER_ID, title=CHAPTER_TITLE,
|
||
version=CHAPTER_VERSION, blocks=blocks)
|