runner-ai-intelligence / src /tools /viz_executor.py
avfranco's picture
HF Space deploy snapshot (minimal allow-list)
557ee65
# src/tools/viz_executor.py
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from typing import List, Dict, Any
from domain.training.charts import ChartSpec, ChartType
from .interfaces import IVisualizationExecutor
from src.tools.runner_ai import calculate_pace_s_per_km
class TemplateVisualizationExecutor(IVisualizationExecutor):
def render_chart(self, chart_spec: ChartSpec, features: List[Dict[str, Any]]) -> plt.Figure:
if chart_spec.chart_type == ChartType.PACE:
return self._render_pace_chart(features, chart_spec.title or "Pace over Time")
elif chart_spec.chart_type == ChartType.HEART_RATE:
return self._render_hr_chart(features, chart_spec.title or "Heart Rate over Time")
else:
return self._empty_chart(f"Unsupported Chart Type: {chart_spec.chart_type}")
def _render_pace_chart(self, features: List[Dict[str, Any]], title: str) -> plt.Figure:
fig, ax = plt.subplots(figsize=(10, 5))
dates = []
paces = []
for run in features:
dt = run.get("start_time")
pace_s = calculate_pace_s_per_km(
run.get("total_distance_m"), run.get("total_duration_s")
)
if dt and pace_s:
dates.append(dt)
# Convert s/km to min/km (float)
paces.append(pace_s / 60.0)
if not dates:
return self._empty_chart(title)
ax.plot(dates, paces, marker="o", linestyle="-", color="blue")
ax.set_title(title)
ax.set_ylabel("Pace (min/km)")
ax.set_xlabel("Date")
ax.grid(True)
# Invert y-axis for pace (smaller min/km is faster)
ax.invert_yaxis()
fig.autofmt_xdate()
return fig
def _render_hr_chart(self, features: List[Dict[str, Any]], title: str) -> plt.Figure:
fig, ax = plt.subplots(figsize=(10, 5))
dates = []
hrs = []
for run in features:
dt = run.get("start_time")
hr = run.get("avg_hr_bpm")
if dt and hr:
dates.append(dt)
hrs.append(hr)
if not dates:
return self._empty_chart(title)
ax.plot(dates, hrs, marker="s", linestyle="--", color="red")
ax.set_title(title)
ax.set_ylabel("Heart Rate (bpm)")
ax.set_xlabel("Date")
ax.grid(True)
fig.autofmt_xdate()
return fig
def _empty_chart(self, title: str) -> plt.Figure:
fig, ax = plt.subplots(figsize=(6, 3))
ax.text(0.5, 0.5, "No Data Available", ha="center", va="center")
ax.set_title(title)
ax.axis("off")
return fig