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

@@ -17,11 +17,11 @@ from typing import Any, Mapping, Optional, Dict
def _build_economy_config(economy_data: Optional[Mapping[str, Any]]) -> EconomyConfig:
"""
Construye EconomyConfig validando tipos y evitando que el type checker
mezcle floats y dicts en un solo diccionario.
Builds EconomyConfig validating types and preventing the type checker
from mixing floats and dicts in a single dictionary.
"""
# Valores por defecto
# Default values
default_customer_segments: Dict[str, str] = {
"VIP": "high",
"Premium": "high",
@@ -45,9 +45,9 @@ def _build_economy_config(economy_data: Optional[Mapping[str, Any]]) -> EconomyC
value = economy_data.get(field, default)
if isinstance(value, (int, float)):
return float(value)
raise ValueError(f"El campo '{field}' debe ser numérico (float). Valor recibido: {value!r}")
raise ValueError(f"The field '{field}' must be numeric (float). Received value: {value!r}")
# Campos escalares
# Scalar fields
labor_cost_per_hour = _get_float("labor_cost_per_hour", 20.0)
overhead_rate = _get_float("overhead_rate", 0.10)
tech_costs_annual = _get_float("tech_costs_annual", 5000.0)
@@ -55,16 +55,16 @@ def _build_economy_config(economy_data: Optional[Mapping[str, Any]]) -> EconomyC
automation_volume_share = _get_float("automation_volume_share", 0.5)
automation_success_rate = _get_float("automation_success_rate", 0.6)
# customer_segments puede venir o no; si viene, validarlo
# customer_segments may or may not be present; if present, validate it
customer_segments: Dict[str, str] = dict(default_customer_segments)
if "customer_segments" in economy_data and economy_data["customer_segments"] is not None:
cs = economy_data["customer_segments"]
if not isinstance(cs, Mapping):
raise ValueError("customer_segments debe ser un diccionario {segment: level}")
raise ValueError("customer_segments must be a dictionary {segment: level}")
for k, v in cs.items():
if not isinstance(v, str):
raise ValueError(
f"El valor de customer_segments['{k}'] debe ser str. Valor recibido: {v!r}"
f"The value of customer_segments['{k}'] must be str. Received value: {v!r}"
)
customer_segments[str(k)] = v
@@ -86,31 +86,31 @@ def run_analysis(
company_folder: Optional[str] = None,
) -> tuple[Path, Optional[Path]]:
"""
Ejecuta el pipeline sobre un CSV y devuelve:
- (results_dir, None) si return_type == "path"
- (results_dir, zip_path) si return_type == "zip"
Executes the pipeline on a CSV and returns:
- (results_dir, None) if return_type == "path"
- (results_dir, zip_path) if return_type == "zip"
input_path puede ser absoluto o relativo, pero los resultados
se escribirán SIEMPRE en la carpeta del CSV, dentro de una
subcarpeta con nombre = timestamp (y opcionalmente prefijada
por company_folder).
input_path can be absolute or relative, but results
will ALWAYS be written to the CSV's folder, inside a
subfolder named timestamp (and optionally prefixed
by company_folder).
"""
input_path = input_path.resolve()
if not input_path.exists():
raise FileNotFoundError(f"El CSV no existe: {input_path}")
raise FileNotFoundError(f"CSV does not exist: {input_path}")
if not input_path.is_file():
raise ValueError(f"La ruta no apunta a un fichero CSV: {input_path}")
raise ValueError(f"Path does not point to a CSV file: {input_path}")
# Carpeta donde está el CSV
# Folder where the CSV is located
csv_dir = input_path.parent
# DataSource y ResultsSink apuntan a ESA carpeta
# DataSource and ResultsSink point to THAT folder
datasource = LocalDataSource(base_dir=str(csv_dir))
sink = LocalResultsSink(base_dir=str(csv_dir))
# Config de economía
# Economy config
economy_cfg = _build_economy_config(economy_data)
dimension_params: Dict[str, Mapping[str, Any]] = {
@@ -119,13 +119,13 @@ def run_analysis(
}
}
# Callback de scoring
# Scoring callback
def agentic_post_run(results: Dict[str, Any], run_base: str, sink_: ResultsSink) -> None:
scorer = AgenticScorer()
try:
agentic = scorer.compute_and_return(results)
except Exception as e:
# No rompemos toda la ejecución si el scorer falla
# Don't break the entire execution if the scorer fails
agentic = {
"error": f"{type(e).__name__}: {e}",
}
@@ -139,45 +139,45 @@ def run_analysis(
post_run=[agentic_post_run],
)
# Timestamp de ejecución (nombre de la carpeta de resultados)
# Execution timestamp (results folder name)
timestamp = datetime.utcnow().strftime("%Y%m%d-%H%M%S")
# Ruta lógica de resultados (RELATIVA al base_dir del sink)
# Logical results path (RELATIVE to sink's base_dir)
if company_folder:
# Ej: "Cliente_X/20251208-153045"
# E.g. "Cliente_X/20251208-153045"
run_dir_rel = f"{company_folder.rstrip('/')}/{timestamp}"
else:
# Ej: "20251208-153045"
# E.g. "20251208-153045"
run_dir_rel = timestamp
# Ejecutar pipeline: el CSV se pasa relativo a csv_dir
# Execute pipeline: CSV is passed relative to csv_dir
pipeline.run(
input_path=input_path.name,
run_dir=run_dir_rel,
)
# Carpeta real con los resultados
# Actual folder with results
results_dir = csv_dir / run_dir_rel
if return_type == "path":
return results_dir, None
# --- ZIP de resultados -------------------------------------------------
# Creamos el ZIP en la MISMA carpeta del CSV, con nombre basado en run_dir
# --- ZIP results -------------------------------------------------------
# Create the ZIP in the SAME folder as the CSV, with name based on run_dir
zip_name = f"{run_dir_rel.replace('/', '_')}.zip"
zip_path = csv_dir / zip_name
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:
for file in results_dir.rglob("*"):
if file.is_file():
# Lo guardamos relativo a la carpeta de resultados
# Store it relative to the results folder
arcname = file.relative_to(results_dir.parent)
zipf.write(file, arcname)
return results_dir, zip_path
from typing import Any, Mapping, Dict # asegúrate de tener estos imports arriba
from typing import Any, Mapping, Dict # ensure these imports are at the top
def run_analysis_collect_json(
@@ -187,33 +187,33 @@ def run_analysis_collect_json(
company_folder: Optional[str] = None,
) -> Dict[str, Any]:
"""
Ejecuta el pipeline y devuelve un único JSON con todos los resultados.
Executes the pipeline and returns a single JSON with all results.
A diferencia de run_analysis:
- NO escribe results.json
- NO escribe agentic_readiness.json
- agentic_readiness se incrusta en el dict de resultados
Unlike run_analysis:
- Does NOT write results.json
- Does NOT write agentic_readiness.json
- agentic_readiness is embedded in the results dict
El parámetro `analysis` permite elegir el nivel de análisis:
The `analysis` parameter allows choosing the analysis level:
- "basic" -> beyond_metrics/configs/basic.json
- "premium" -> beyond_metrics/configs/beyond_metrics_config.json
"""
# Normalizamos y validamos la ruta del CSV
# Normalize and validate the CSV path
input_path = input_path.resolve()
if not input_path.exists():
raise FileNotFoundError(f"El CSV no existe: {input_path}")
raise FileNotFoundError(f"CSV does not exist: {input_path}")
if not input_path.is_file():
raise ValueError(f"La ruta no apunta a un fichero CSV: {input_path}")
raise ValueError(f"Path does not point to a CSV file: {input_path}")
# Carpeta donde está el CSV
# Folder where the CSV is located
csv_dir = input_path.parent
# DataSource y ResultsSink apuntan a ESA carpeta
# DataSource and ResultsSink point to THAT folder
datasource = LocalDataSource(base_dir=str(csv_dir))
sink = LocalResultsSink(base_dir=str(csv_dir))
# Config de economía
# Economy config
economy_cfg = _build_economy_config(economy_data)
dimension_params: Dict[str, Mapping[str, Any]] = {
@@ -222,13 +222,13 @@ def run_analysis_collect_json(
}
}
# Elegimos el fichero de configuración de dimensiones según `analysis`
# Choose the dimensions config file based on `analysis`
if analysis == "basic":
dimensions_config_path = "beyond_metrics/configs/basic.json"
else:
dimensions_config_path = "beyond_metrics/configs/beyond_metrics_config.json"
# Callback post-run: añadir agentic_readiness al JSON final (sin escribir ficheros)
# Post-run callback: add agentic_readiness to the final JSON (without writing files)
def agentic_post_run(results: Dict[str, Any], run_base: str, sink_: ResultsSink) -> None:
scorer = AgenticScorer()
try:
@@ -245,14 +245,14 @@ def run_analysis_collect_json(
post_run=[agentic_post_run],
)
# Timestamp de ejecución (para separar posibles artefactos como plots)
# Execution timestamp (to separate possible artifacts like plots)
timestamp = datetime.utcnow().strftime("%Y%m%d-%H%M%S")
if company_folder:
run_dir_rel = f"{company_folder.rstrip('/')}/{timestamp}"
else:
run_dir_rel = timestamp
# Ejecutar pipeline sin escribir results.json
# Execute pipeline without writing results.json
results = pipeline.run(
input_path=input_path.name,
run_dir=run_dir_rel,