# tools/plot_generator.py import os import tempfile import pandas as pd import plotly.graph_objects as go from typing import Tuple, Union def plot_metric_tool( file_path: str, date_col: str, value_col: str, output_dir: str = "/tmp", title: str = None, line_width: int = 2, marker_size: int = 6 ) -> Union[Tuple[go.Figure, str], str]: """ Load CSV or Excel file, parse a time series metric, and return an interactive Plotly Figure plus a high-res PNG file path for static embedding. Returns: - (fig, img_path) on success - error string starting with '❌' on failure """ # Load data ext = os.path.splitext(file_path)[1].lower() try: df = pd.read_excel(file_path) if ext in ('.xls', '.xlsx') else pd.read_csv(file_path) except Exception as exc: return f"❌ Failed to load file: {exc}" # Validate columns missing = [c for c in (date_col, value_col) if c not in df.columns] if missing: return f"❌ Missing column(s): {', '.join(missing)}" # Parse and clean try: df[date_col] = pd.to_datetime(df[date_col], errors='coerce') except Exception: return f"❌ Could not parse '{date_col}' as dates." df[value_col] = pd.to_numeric(df[value_col], errors='coerce') df = df.dropna(subset=[date_col, value_col]) if df.empty: return f"❌ No valid data after cleaning '{date_col}'/'{value_col}'" # Aggregate duplicates and sort df = ( df[[date_col, value_col]] .groupby(date_col, as_index=True) .mean() .sort_index() ) # Build figure fig = go.Figure( data=[ go.Scatter( x=df.index, y=df[value_col], mode='lines+markers', line=dict(width=line_width), marker=dict(size=marker_size), name=value_col, ) ] ) fig.update_layout( title=title or f"{value_col} Trend", xaxis_title=date_col, yaxis_title=value_col, template='plotly_dark', hovermode='x unified' ) # Save PNG os.makedirs(output_dir, exist_ok=True) tmp = tempfile.NamedTemporaryFile(suffix='.png', prefix='trend_', dir=output_dir, delete=False) img_path = tmp.name tmp.close() try: fig.write_image(img_path, scale=2) except Exception as exc: return f"❌ Failed saving image: {exc}" return fig, img_path