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:
@@ -14,25 +14,25 @@ from openai import OpenAI
|
||||
|
||||
|
||||
DEFAULT_SYSTEM_PROMPT = (
|
||||
"Eres un consultor experto en contact centers. "
|
||||
"Vas a recibir resultados analíticos de un sistema de métricas "
|
||||
"(BeyondMetrics) en formato JSON. Tu tarea es generar un informe claro, "
|
||||
"accionable y orientado a negocio, destacando los principales hallazgos, "
|
||||
"riesgos y oportunidades de mejora."
|
||||
"You are an expert contact center consultant. "
|
||||
"You will receive analytical results from a metrics system "
|
||||
"(BeyondMetrics) in JSON format. Your task is to generate a clear, "
|
||||
"actionable, business-oriented report, highlighting the main findings, "
|
||||
"risks, and opportunities for improvement."
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ReportAgentConfig:
|
||||
"""
|
||||
Configuración básica del agente de informes.
|
||||
Basic configuration for the report agent.
|
||||
|
||||
openai_api_key:
|
||||
Se puede pasar explícitamente o leer de la variable de entorno OPENAI_API_KEY.
|
||||
Can be passed explicitly or read from the OPENAI_API_KEY environment variable.
|
||||
model:
|
||||
Modelo de ChatGPT a utilizar, p.ej. 'gpt-4.1-mini' o similar.
|
||||
ChatGPT model to use, e.g. 'gpt-4.1-mini' or similar.
|
||||
system_prompt:
|
||||
Prompt de sistema para controlar el estilo del informe.
|
||||
System prompt to control the report style.
|
||||
"""
|
||||
|
||||
openai_api_key: Optional[str] = None
|
||||
@@ -42,15 +42,15 @@ class ReportAgentConfig:
|
||||
|
||||
class BeyondMetricsReportAgent:
|
||||
"""
|
||||
Agente muy sencillo que:
|
||||
Simple agent that:
|
||||
|
||||
1) Lee el JSON de resultados de una ejecución de BeyondMetrics.
|
||||
2) Construye un prompt con esos resultados.
|
||||
3) Llama a ChatGPT para generar un informe en texto.
|
||||
4) Guarda el informe en un PDF en disco, EMBEBIENDO las imágenes PNG
|
||||
generadas por el pipeline como anexos.
|
||||
1) Reads the JSON results from a BeyondMetrics execution.
|
||||
2) Builds a prompt with those results.
|
||||
3) Calls ChatGPT to generate a text report.
|
||||
4) Saves the report to a PDF on disk, EMBEDDING the PNG images
|
||||
generated by the pipeline as attachments.
|
||||
|
||||
MVP: centrado en texto + figuras incrustadas.
|
||||
MVP: focused on text + embedded figures.
|
||||
"""
|
||||
|
||||
def __init__(self, config: Optional[ReportAgentConfig] = None) -> None:
|
||||
@@ -59,16 +59,16 @@ class BeyondMetricsReportAgent:
|
||||
api_key = self.config.openai_api_key or os.getenv("OPENAI_API_KEY")
|
||||
if not api_key:
|
||||
raise RuntimeError(
|
||||
"Falta la API key de OpenAI. "
|
||||
"Pásala en ReportAgentConfig(openai_api_key=...) o "
|
||||
"define la variable de entorno OPENAI_API_KEY."
|
||||
"Missing OpenAI API key. "
|
||||
"Pass it in ReportAgentConfig(openai_api_key=...) or "
|
||||
"define the OPENAI_API_KEY environment variable."
|
||||
)
|
||||
|
||||
# Cliente de la nueva API de OpenAI
|
||||
# New OpenAI API client
|
||||
self._client = OpenAI(api_key=api_key)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# API pública principal
|
||||
# Main public API
|
||||
# ------------------------------------------------------------------
|
||||
def generate_pdf_report(
|
||||
self,
|
||||
@@ -77,48 +77,48 @@ class BeyondMetricsReportAgent:
|
||||
extra_user_prompt: str = "",
|
||||
) -> str:
|
||||
"""
|
||||
Genera un informe en PDF a partir de una carpeta de resultados.
|
||||
Generates a PDF report from a results folder.
|
||||
|
||||
Parámetros:
|
||||
Parameters:
|
||||
- run_base:
|
||||
Carpeta base de la ejecución. Debe contener al menos 'results.json'
|
||||
y, opcionalmente, imágenes PNG generadas por el pipeline.
|
||||
Base folder for the execution. Must contain at least 'results.json'
|
||||
and, optionally, PNG images generated by the pipeline.
|
||||
- output_pdf_path:
|
||||
Ruta completa del PDF de salida. Si es None, se crea
|
||||
'beyondmetrics_report.pdf' dentro de run_base.
|
||||
Full path for the output PDF. If None, creates
|
||||
'beyondmetrics_report.pdf' inside run_base.
|
||||
- extra_user_prompt:
|
||||
Texto adicional para afinar la petición al agente
|
||||
(p.ej. "enfatiza eficiencia y SLA", etc.)
|
||||
Additional text to refine the agent's request
|
||||
(e.g. "emphasize efficiency and SLA", etc.)
|
||||
|
||||
Devuelve:
|
||||
- La ruta del PDF generado.
|
||||
Returns:
|
||||
- The path to the generated PDF.
|
||||
"""
|
||||
run_dir = Path(run_base)
|
||||
results_json = run_dir / "results.json"
|
||||
if not results_json.exists():
|
||||
raise FileNotFoundError(
|
||||
f"No se ha encontrado {results_json}. "
|
||||
"Asegúrate de ejecutar primero el pipeline."
|
||||
f"{results_json} not found. "
|
||||
"Make sure to run the pipeline first."
|
||||
)
|
||||
|
||||
# 1) Leer JSON de resultados
|
||||
# 1) Read results JSON
|
||||
with results_json.open("r", encoding="utf-8") as f:
|
||||
results_data: Dict[str, Any] = json.load(f)
|
||||
|
||||
# 2) Buscar imágenes generadas
|
||||
# 2) Find generated images
|
||||
image_files = sorted(p for p in run_dir.glob("*.png"))
|
||||
|
||||
# 3) Construir prompt de usuario
|
||||
# 3) Build user prompt
|
||||
user_prompt = self._build_user_prompt(
|
||||
results=results_data,
|
||||
image_files=[p.name for p in image_files],
|
||||
extra_user_prompt=extra_user_prompt,
|
||||
)
|
||||
|
||||
# 4) Llamar a ChatGPT para obtener el texto del informe
|
||||
# 4) Call ChatGPT to get the report text
|
||||
report_text = self._call_chatgpt(user_prompt)
|
||||
|
||||
# 5) Crear PDF con texto + imágenes embebidas
|
||||
# 5) Create PDF with text + embedded images
|
||||
if output_pdf_path is None:
|
||||
output_pdf_path = str(run_dir / "beyondmetrics_report.pdf")
|
||||
|
||||
@@ -127,7 +127,7 @@ class BeyondMetricsReportAgent:
|
||||
return output_pdf_path
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Construcción del prompt
|
||||
# Prompt construction
|
||||
# ------------------------------------------------------------------
|
||||
def _build_user_prompt(
|
||||
self,
|
||||
@@ -136,34 +136,34 @@ class BeyondMetricsReportAgent:
|
||||
extra_user_prompt: str = "",
|
||||
) -> str:
|
||||
"""
|
||||
Construye el mensaje de usuario que se enviará al modelo.
|
||||
Para un MVP, serializamos el JSON de resultados entero.
|
||||
Más adelante se puede resumir si el JSON crece demasiado.
|
||||
Builds the user message to be sent to the model.
|
||||
For an MVP, we serialize the entire results JSON.
|
||||
Later, this can be summarized if the JSON grows too large.
|
||||
"""
|
||||
results_str = json.dumps(results, indent=2, ensure_ascii=False)
|
||||
|
||||
images_section = (
|
||||
"Imágenes generadas en la ejecución:\n"
|
||||
"Images generated in the execution:\n"
|
||||
+ "\n".join(f"- {name}" for name in image_files)
|
||||
if image_files
|
||||
else "No se han generado imágenes en esta ejecución."
|
||||
else "No images were generated in this execution."
|
||||
)
|
||||
|
||||
extra = (
|
||||
f"\n\nInstrucciones adicionales del usuario:\n{extra_user_prompt}"
|
||||
f"\n\nAdditional user instructions:\n{extra_user_prompt}"
|
||||
if extra_user_prompt
|
||||
else ""
|
||||
)
|
||||
|
||||
prompt = (
|
||||
"A continuación te proporciono los resultados de una ejecución de BeyondMetrics "
|
||||
"en formato JSON. Debes elaborar un INFORME EJECUTIVO para un cliente de "
|
||||
"contact center. El informe debe incluir:\n"
|
||||
"- Resumen ejecutivo en lenguaje de negocio.\n"
|
||||
"- Principales hallazgos por dimensión.\n"
|
||||
"- Riesgos o problemas detectados.\n"
|
||||
"- Recomendaciones accionables.\n\n"
|
||||
"Resultados (JSON):\n"
|
||||
"Below I provide you with the results of a BeyondMetrics execution "
|
||||
"in JSON format. You must produce an EXECUTIVE REPORT for a contact "
|
||||
"center client. The report should include:\n"
|
||||
"- Executive summary in business language.\n"
|
||||
"- Main findings by dimension.\n"
|
||||
"- Detected risks or issues.\n"
|
||||
"- Actionable recommendations.\n\n"
|
||||
"Results (JSON):\n"
|
||||
f"{results_str}\n\n"
|
||||
f"{images_section}"
|
||||
f"{extra}"
|
||||
@@ -172,12 +172,12 @@ class BeyondMetricsReportAgent:
|
||||
return prompt
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Llamada a ChatGPT (nueva API)
|
||||
# ChatGPT call (new API)
|
||||
# ------------------------------------------------------------------
|
||||
def _call_chatgpt(self, user_prompt: str) -> str:
|
||||
"""
|
||||
Llama al modelo de ChatGPT y devuelve el contenido del mensaje de respuesta.
|
||||
Implementado con la nueva API de OpenAI.
|
||||
Calls the ChatGPT model and returns the content of the response message.
|
||||
Implemented with the new OpenAI API.
|
||||
"""
|
||||
resp = self._client.chat.completions.create(
|
||||
model=self.config.model,
|
||||
@@ -190,11 +190,11 @@ class BeyondMetricsReportAgent:
|
||||
|
||||
content = resp.choices[0].message.content
|
||||
if not isinstance(content, str):
|
||||
raise RuntimeError("La respuesta del modelo no contiene texto.")
|
||||
raise RuntimeError("The model response does not contain text.")
|
||||
return content
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Escritura de PDF (texto + imágenes)
|
||||
# PDF writing (text + images)
|
||||
# ------------------------------------------------------------------
|
||||
def _write_pdf(
|
||||
self,
|
||||
@@ -203,11 +203,11 @@ class BeyondMetricsReportAgent:
|
||||
image_paths: Sequence[Path],
|
||||
) -> None:
|
||||
"""
|
||||
Crea un PDF A4 con:
|
||||
Creates an A4 PDF with:
|
||||
|
||||
1) Texto del informe (páginas iniciales).
|
||||
2) Una sección de anexos donde se incrustan las imágenes PNG
|
||||
generadas por el pipeline, escaladas para encajar en la página.
|
||||
1) Report text (initial pages).
|
||||
2) An appendix section where the PNG images generated by the
|
||||
pipeline are embedded, scaled to fit the page.
|
||||
"""
|
||||
output_path = str(output_path)
|
||||
c = canvas.Canvas(output_path, pagesize=A4)
|
||||
@@ -220,7 +220,7 @@ class BeyondMetricsReportAgent:
|
||||
|
||||
c.setFont("Helvetica", 11)
|
||||
|
||||
# --- Escribir texto principal ---
|
||||
# --- Write main text ---
|
||||
def _wrap_line(line: str, max_chars: int = 100) -> list[str]:
|
||||
parts: list[str] = []
|
||||
current: list[str] = []
|
||||
@@ -248,37 +248,37 @@ class BeyondMetricsReportAgent:
|
||||
c.drawString(margin_x, y, line)
|
||||
y -= line_height
|
||||
|
||||
# --- Anexar imágenes como figuras ---
|
||||
# --- Append images as figures ---
|
||||
if image_paths:
|
||||
# Nueva página para las figuras
|
||||
# New page for figures
|
||||
c.showPage()
|
||||
c.setFont("Helvetica-Bold", 14)
|
||||
c.drawString(margin_x, height - margin_y, "Anexo: Figuras")
|
||||
c.drawString(margin_x, height - margin_y, "Appendix: Figures")
|
||||
c.setFont("Helvetica", 11)
|
||||
|
||||
current_y = height - margin_y - 2 * line_height
|
||||
|
||||
for img_path in image_paths:
|
||||
# Si no cabe la imagen en la página, pasamos a la siguiente
|
||||
# If the image doesn't fit on the page, move to the next one
|
||||
available_height = current_y - margin_y
|
||||
if available_height < 100: # espacio mínimo
|
||||
if available_height < 100: # minimum space
|
||||
c.showPage()
|
||||
c.setFont("Helvetica-Bold", 14)
|
||||
c.drawString(margin_x, height - margin_y, "Anexo: Figuras (cont.)")
|
||||
c.drawString(margin_x, height - margin_y, "Appendix: Figures (cont.)")
|
||||
c.setFont("Helvetica", 11)
|
||||
current_y = height - margin_y - 2 * line_height
|
||||
available_height = current_y - margin_y
|
||||
|
||||
# Título de la figura
|
||||
title = f"Figura: {img_path.name}"
|
||||
# Figure title
|
||||
title = f"Figure: {img_path.name}"
|
||||
c.drawString(margin_x, current_y, title)
|
||||
current_y -= line_height
|
||||
|
||||
# Cargar imagen y escalarla
|
||||
# Load and scale image
|
||||
try:
|
||||
img = ImageReader(str(img_path))
|
||||
iw, ih = img.getSize()
|
||||
# Escala para encajar en ancho y alto disponibles
|
||||
# Scale to fit available width and height
|
||||
max_img_height = available_height - 2 * line_height
|
||||
scale = min(max_width / iw, max_img_height / ih)
|
||||
if scale <= 0:
|
||||
@@ -302,8 +302,8 @@ class BeyondMetricsReportAgent:
|
||||
|
||||
current_y = y_img - 2 * line_height
|
||||
except Exception as e:
|
||||
# Si falla la carga, lo indicamos en el PDF
|
||||
err_msg = f"No se pudo cargar la imagen {img_path.name}: {e}"
|
||||
# If loading fails, indicate it in the PDF
|
||||
err_msg = f"Could not load image {img_path.name}: {e}"
|
||||
c.drawString(margin_x, current_y, err_msg)
|
||||
current_y -= 2 * line_height
|
||||
|
||||
|
||||
Reference in New Issue
Block a user