File size: 3,188 Bytes
de6f1e8
ccdbd61
 
 
 
60034d4
 
ccdbd61
 
010071f
b8b8eab
ccdbd61
 
 
010071f
60034d4
 
 
 
 
 
ccdbd61
60034d4
ccdbd61
 
b8b8eab
ccdbd61
60034d4
ccdbd61
 
 
 
b8b8eab
ccdbd61
60034d4
 
ccdbd61
 
 
 
 
60034d4
 
b8b8eab
ccdbd61
60034d4
 
 
b04cfbb
ccdbd61
 
 
60034d4
 
ccdbd61
60034d4
ccdbd61
60034d4
 
 
 
 
 
b8b8eab
ccdbd61
b8b8eab
ccdbd61
 
 
 
 
 
 
 
b8b8eab
60034d4
de6f1e8
60034d4
 
ccdbd61
 
60034d4
 
ccdbd61
60034d4
ccdbd61
 
 
 
de6f1e8
60034d4
ccdbd61
60034d4
 
 
ccdbd61
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# tools/plot_generator.py
# ------------------------------------------------------------
# Creates an interactive line‑and‑marker trend chart for any
# (date_col, value_col) pair and saves a hi‑res PNG copy.

import os
import tempfile
from typing import Tuple, Union

import pandas as pd
import plotly.graph_objects as go

# Alias for typing β€” every helper returns a go.Figure
Plot = go.Figure


def plot_metric_tool(
    file_path: str,
    date_col: str,
    value_col: str,
    output_dir: str = "/tmp",
    title: str | None = None,
    line_width: int = 2,
    marker_size: int = 6,
) -> Union[Tuple[Plot, str], str]:
    """
    Build a (date, metric) trend chart.

    Returns
    -------
    (fig, png_path)  on success
    error string     on failure (string starts with '❌')
    """
    # ── 1. Load CSV or Excel ──────────────────────────────────
    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}"

    # ── 2. 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)}"

    # ── 3. Parse & clean ──────────────────────────────────────
    df[date_col] = pd.to_datetime(df[date_col], errors="coerce")
    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 duplicate timestamps, sort by date
    df = (
        df[[date_col, value_col]]
        .groupby(date_col, as_index=True)
        .mean()
        .sort_index()
    )

    # ── 4. Build Plotly figure ────────────────────────────────
    fig = go.Figure(
        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",
    )

    # ── 5. Save static PNG copy ───────────────────────────────
    os.makedirs(output_dir, exist_ok=True)
    tmp = tempfile.NamedTemporaryFile(
        prefix="trend_", suffix=".png", dir=output_dir, delete=False
    )
    png_path = tmp.name
    tmp.close()
    try:
        fig.write_image(png_path, scale=2)
    except Exception as exc:
        return f"❌ Failed saving image: {exc}"

    return fig, png_path