Translate Phase 3 low-priority backend files (complete Spanish-to-English translation)

Phase 3 of Spanish-to-English translation for low-priority backend files:

Backend core modules (4 files):
- Volumetria.py: Translated ~15 occurrences (docstrings, comments, plot labels, day abbreviations)
- agent.py: Translated ~15 occurrences (system prompts, docstrings, error messages)
- pipeline.py: Translated ~10 occurrences (log messages, docstrings, comments)
- analysis_service.py: Translated ~10 occurrences (docstrings, error messages, comments)

All function names, class names, and variable names preserved for API compatibility.
Frontend and backend compilation tested and verified successful.

This completes the comprehensive Spanish-to-English translation project:
- Phase 1 (High Priority): 3 files - backendMapper.ts, analysisGenerator.ts, realDataAnalysis.ts
- Phase 2 (Medium Priority): 5 files - dataTransformation.ts, segmentClassifier.ts, + 3 dimension files
- Phase 3 (Low Priority): 4 files - Volumetria.py, agent.py, pipeline.py, analysis_service.py

Total files translated: 12 files (5 frontend TypeScript + 7 backend Python)
All critical path translations complete.

Related to TRANSLATION_STATUS.md Phase 3 completion.

https://claude.ai/code/session_01GNbnkFoESkRcnPr3bLCYDg
This commit is contained in:
Claude
2026-02-07 11:15:47 +00:00
parent 8c7f5fa827
commit 9caa382010
4 changed files with 217 additions and 217 deletions

View File

@@ -23,7 +23,7 @@ LOGGER = logging.getLogger(__name__)
def setup_basic_logging(level: str = "INFO") -> None:
"""
Configuración básica de logging, por si se necesita desde scripts.
Basic logging configuration, if needed from scripts.
"""
logging.basicConfig(
level=getattr(logging, level.upper(), logging.INFO),
@@ -33,10 +33,10 @@ def setup_basic_logging(level: str = "INFO") -> None:
def _import_class(path: str) -> type:
"""
Import dinámico de una clase a partir de un string tipo:
Dynamic import of a class from a string like:
"beyond_metrics.dimensions.VolumetriaMetrics"
"""
LOGGER.debug("Importando clase %s", path)
LOGGER.debug("Importing class %s", path)
module_name, class_name = path.rsplit(".", 1)
module = import_module(module_name)
cls = getattr(module, class_name)
@@ -45,7 +45,7 @@ def _import_class(path: str) -> type:
def _serialize_for_json(obj: Any) -> Any:
"""
Convierte objetos típicos de numpy/pandas en tipos JSON-friendly.
Converts typical numpy/pandas objects to JSON-friendly types.
"""
if obj is None or isinstance(obj, (str, int, float, bool)):
return obj
@@ -73,12 +73,12 @@ PostRunCallback = Callable[[Dict[str, Any], str, ResultsSink], None]
@dataclass
class BeyondMetricsPipeline:
"""
Pipeline principal de BeyondMetrics.
Main BeyondMetrics pipeline.
- Lee un CSV desde un DataSource (local, S3, Google Drive, etc.).
- Ejecuta dimensiones configuradas en un dict de configuración.
- Serializa resultados numéricos/tabulares a JSON.
- Guarda las imágenes de los métodos que comienzan por 'plot_'.
- Reads a CSV from a DataSource (local, S3, Google Drive, etc.).
- Executes dimensions configured in a config dict.
- Serializes numeric/tabular results to JSON.
- Saves images from methods starting with 'plot_'.
"""
datasource: DataSource
@@ -95,39 +95,39 @@ class BeyondMetricsPipeline:
write_results_json: bool = True,
) -> Dict[str, Any]:
LOGGER.info("Inicio de ejecución de BeyondMetricsPipeline")
LOGGER.info("Leyendo CSV de entrada: %s", input_path)
LOGGER.info("Starting BeyondMetricsPipeline execution")
LOGGER.info("Reading input CSV: %s", input_path)
# 1) Leer datos
# 1) Read data
df = self.datasource.read_csv(input_path)
LOGGER.info("CSV leído con %d filas y %d columnas", df.shape[0], df.shape[1])
LOGGER.info("CSV read with %d rows and %d columns", df.shape[0], df.shape[1])
# 2) Determinar carpeta/base de salida para esta ejecución
# 2) Determine output folder/base for this execution
run_base = run_dir.rstrip("/")
LOGGER.info("Ruta base de esta ejecución: %s", run_base)
LOGGER.info("Base path for this execution: %s", run_base)
# 3) Ejecutar dimensiones
# 3) Execute dimensions
dimensions_cfg = self.dimensions_config
if not isinstance(dimensions_cfg, dict):
raise ValueError("El bloque 'dimensions' debe ser un dict.")
raise ValueError("The 'dimensions' block must be a dict.")
all_results: Dict[str, Any] = {}
for dim_name, dim_cfg in dimensions_cfg.items():
if not isinstance(dim_cfg, dict):
raise ValueError(f"Config inválida para dimensión '{dim_name}' (debe ser dict).")
raise ValueError(f"Invalid config for dimension '{dim_name}' (must be dict).")
if not dim_cfg.get("enabled", True):
LOGGER.info("Dimensión '%s' desactivada; se omite.", dim_name)
LOGGER.info("Dimension '%s' disabled; skipping.", dim_name)
continue
class_path = dim_cfg.get("class")
if not class_path:
raise ValueError(f"Falta 'class' en la dimensión '{dim_name}'.")
raise ValueError(f"Missing 'class' in dimension '{dim_name}'.")
metrics: List[str] = dim_cfg.get("metrics", [])
if not metrics:
LOGGER.info("Dimensión '%s' sin métricas configuradas; se omite.", dim_name)
LOGGER.info("Dimension '%s' has no configured metrics; skipping.", dim_name)
continue
cls = _import_class(class_path)
@@ -136,35 +136,35 @@ class BeyondMetricsPipeline:
if self.dimension_params is not None:
extra_kwargs = self.dimension_params.get(dim_name, {}) or {}
# Las dimensiones reciben df en el constructor
# Dimensions receive df in the constructor
instance = cls(df, **extra_kwargs)
dim_results: Dict[str, Any] = {}
for metric_name in metrics:
LOGGER.info(" - Ejecutando métrica '%s.%s'", dim_name, metric_name)
LOGGER.info(" - Executing metric '%s.%s'", dim_name, metric_name)
result = self._execute_metric(instance, metric_name, run_base, dim_name)
dim_results[metric_name] = result
all_results[dim_name] = dim_results
# 4) Guardar JSON de resultados (opcional)
# 4) Save results JSON (optional)
if write_results_json:
results_json_path = f"{run_base}/results.json"
LOGGER.info("Guardando resultados en JSON: %s", results_json_path)
LOGGER.info("Saving results to JSON: %s", results_json_path)
self.sink.write_json(results_json_path, all_results)
# 5) Ejecutar callbacks post-run (scorers, agentes, etc.)
# 5) Execute post-run callbacks (scorers, agents, etc.)
if self.post_run:
LOGGER.info("Ejecutando %d callbacks post-run...", len(self.post_run))
LOGGER.info("Executing %d post-run callbacks...", len(self.post_run))
for cb in self.post_run:
try:
LOGGER.info("Ejecutando post-run callback: %s", cb)
LOGGER.info("Executing post-run callback: %s", cb)
cb(all_results, run_base, self.sink)
except Exception:
LOGGER.exception("Error ejecutando post-run callback %s", cb)
LOGGER.exception("Error executing post-run callback %s", cb)
LOGGER.info("Ejecución completada correctamente.")
LOGGER.info("Execution completed successfully.")
return all_results
@@ -176,42 +176,42 @@ class BeyondMetricsPipeline:
dim_name: str,
) -> Any:
"""
Ejecuta una métrica:
Executes a metric:
- Si empieza por 'plot_' -> se asume que devuelve Axes:
- se guarda la figura como PNG
- se devuelve {"type": "image", "path": "..."}
- Si no, se serializa el valor a JSON.
- If it starts with 'plot_' -> assumed to return Axes:
- the figure is saved as PNG
- returns {"type": "image", "path": "..."}
- Otherwise, the value is serialized to JSON.
Además, para métricas categóricas (por skill/canal) de la dimensión
'volumetry', devolvemos explícitamente etiquetas y valores para que
el frontend pueda saber a qué pertenece cada número.
Additionally, for categorical metrics (by skill/channel) from the
'volumetry' dimension, we explicitly return labels and values so
the frontend can know what each number belongs to.
"""
method = getattr(instance, metric_name, None)
if method is None or not callable(method):
raise ValueError(
f"La métrica '{metric_name}' no existe en {type(instance).__name__}"
f"Metric '{metric_name}' does not exist in {type(instance).__name__}"
)
# Caso plots
# Plot case
if metric_name.startswith("plot_"):
ax = method()
if not isinstance(ax, Axes):
raise TypeError(
f"La métrica '{metric_name}' de '{type(instance).__name__}' "
f"debería devolver un matplotlib.axes.Axes"
f"Metric '{metric_name}' of '{type(instance).__name__}' "
f"should return a matplotlib.axes.Axes"
)
fig = ax.get_figure()
if fig is None:
raise RuntimeError(
"Axes.get_figure() devolvió None, lo cual no debería pasar."
"Axes.get_figure() returned None, which should not happen."
)
fig = cast(Figure, fig)
filename = f"{dim_name}_{metric_name}.png"
img_path = f"{run_base}/{filename}"
LOGGER.debug("Guardando figura en %s", img_path)
LOGGER.debug("Saving figure to %s", img_path)
self.sink.write_figure(img_path, fig)
plt.close(fig)
@@ -220,12 +220,12 @@ class BeyondMetricsPipeline:
"path": img_path,
}
# Caso numérico/tabular
# Numeric/tabular case
value = method()
# Caso especial: series categóricas de volumetría (por skill / canal)
# Devolvemos {"labels": [...], "values": [...]} para mantener la
# información de etiquetas en el JSON.
# Special case: categorical series from volumetry (by skill / channel)
# Return {"labels": [...], "values": [...]} to maintain
# label information in the JSON.
if (
dim_name == "volumetry"
and isinstance(value, pd.Series)
@@ -238,7 +238,7 @@ class BeyondMetricsPipeline:
}
):
labels = [str(idx) for idx in value.index.tolist()]
# Aseguramos que todos los valores sean numéricos JSON-friendly
# Ensure all values are JSON-friendly numeric
values = [float(v) for v in value.astype(float).tolist()]
return {
"labels": labels,
@@ -251,7 +251,7 @@ class BeyondMetricsPipeline:
def load_dimensions_config(path: str) -> Dict[str, Any]:
"""
Carga un JSON de configuración que contiene solo el bloque 'dimensions'.
Loads a JSON configuration file containing only the 'dimensions' block.
"""
import json
from pathlib import Path
@@ -261,7 +261,7 @@ def load_dimensions_config(path: str) -> Dict[str, Any]:
dimensions = cfg.get("dimensions")
if dimensions is None:
raise ValueError("El fichero de configuración debe contener un bloque 'dimensions'.")
raise ValueError("The configuration file must contain a 'dimensions' block.")
return dimensions
@@ -274,12 +274,12 @@ def build_pipeline(
post_run: Optional[List[PostRunCallback]] = None,
) -> BeyondMetricsPipeline:
"""
Crea un BeyondMetricsPipeline a partir de:
- ruta al JSON con dimensiones/métricas
- un DataSource ya construido (local/S3/Drive)
- un ResultsSink ya construido (local/S3/Drive)
- una lista opcional de callbacks post_run que se ejecutan al final
(útil para scorers, agentes de IA, etc.)
Creates a BeyondMetricsPipeline from:
- path to JSON with dimensions/metrics
- an already constructed DataSource (local/S3/Drive)
- an already constructed ResultsSink (local/S3/Drive)
- an optional list of post_run callbacks that execute at the end
(useful for scorers, AI agents, etc.)
"""
dims_cfg = load_dimensions_config(dimensions_config_path)
return BeyondMetricsPipeline(