#!/usr/bin/env python3 from __future__ import annotations from dataclasses import dataclass from pathlib import Path import matplotlib.pyplot as plt import pandas as pd ROOT = Path(__file__).resolve().parents[1] PLOTS_DIR = ROOT / "artifacts" / "plots" @dataclass(frozen=True) class SaveSpec: stem: str title: str ylabel: str ylim: tuple[float, float] | None = (0.0, 1.05) METHOD_ORDER = ["MChatbot (ours)", "GPT-5.3", "Grok-4", "Claude-Haiku"] OURS_COLOR = "#1f77b4" BASELINE_COLOR = "#9aa0a6" def _style_axes(ax: plt.Axes, title: str, ylabel: str, ylim: tuple[float, float] | None) -> None: ax.set_title(title) ax.set_ylabel(ylabel) if ylim is not None: ax.set_ylim(*ylim) ax.grid(axis="y", alpha=0.25) ax.set_axisbelow(True) def _bar_with_labels(ax: plt.Axes, x: list[str], y: list[float], colors: list[str]) -> None: bars = ax.bar(x, y, color=colors) for b, v in zip(bars, y): ax.text( b.get_x() + b.get_width() / 2, b.get_height() + 0.015, f"{v:.3f}".rstrip("0").rstrip("."), ha="center", va="bottom", fontsize=9, ) def _save(fig: plt.Figure, stem: str) -> None: PLOTS_DIR.mkdir(parents=True, exist_ok=True) fig.tight_layout() fig.savefig(PLOTS_DIR / f"{stem}.png", dpi=300, bbox_inches="tight") fig.savefig(PLOTS_DIR / f"{stem}.pdf", bbox_inches="tight") plt.close(fig) def plot_qna_accuracy() -> None: df = pd.DataFrame( { "Method": METHOD_ORDER, "QnA Accuracy": [0.875, 0.750, 0.775, 0.800], } ) colors = [OURS_COLOR] + [BASELINE_COLOR] * (len(df) - 1) fig, ax = plt.subplots(figsize=(6.6, 3.6)) _bar_with_labels(ax, df["Method"].tolist(), df["QnA Accuracy"].tolist(), colors) _style_axes(ax, "EA: QnA Accuracy (40 queries)", "Accuracy", (0.0, 1.0)) ax.tick_params(axis="x", rotation=10) _save(fig, "ea_qna_accuracy") def plot_model_building() -> None: df = pd.DataFrame( { "Method": METHOD_ORDER, "MCR": [1.00, 0.30, 0.90, 1.00], "MAC": [0.82, 0.12, 0.56, 0.68], } ) fig, axes = plt.subplots(1, 2, figsize=(9.2, 3.6), sharey=True) for ax, metric, title in zip( axes, ["MCR", "MAC"], ["EB: Model Compilation Rate (MCR)", "EB: Model Answer Correctness (MAC)"], ): colors = [OURS_COLOR] + [BASELINE_COLOR] * (len(df) - 1) _bar_with_labels(ax, df["Method"].tolist(), df[metric].tolist(), colors) _style_axes(ax, title, metric, (0.0, 1.0)) ax.tick_params(axis="x", rotation=15) _save(fig, "eb_model_building_mcr_mac") def plot_query_translation() -> None: df = pd.DataFrame( { "Method": METHOD_ORDER, "QCR": [1.000, 0.075, 0.075, 0.525], "QAR": [0.725, 0.075, 0.075, 0.450], "EM": [0.450, 0.050, 0.025, 0.425], "Token F1": [0.743, 0.641, 0.586, 0.725], } ) fig, axes = plt.subplots(2, 2, figsize=(10.2, 6.6), sharex=True) metrics = [("QCR", "QCR"), ("QAR", "QAR"), ("EM", "Exact Match"), ("Token F1", "Token F1")] for ax, (col, ylabel) in zip(axes.flatten(), metrics): colors = [OURS_COLOR] + [BASELINE_COLOR] * (len(df) - 1) _bar_with_labels(ax, df["Method"].tolist(), df[col].tolist(), colors) _style_axes(ax, f"EB.2: {col}", ylabel, (0.0, 1.0)) ax.tick_params(axis="x", rotation=15) _save(fig, "eb2_query_translation_qcr_qar_em_f1") def plot_ablation() -> None: df = pd.DataFrame( { "Configuration": ["Full MChatbot", "No IR (direct generation)", "No repair loop"], "MCR": [1.00, 0.60, 0.80], "MAC": [0.84, 0.32, 0.58], } ) fig, axes = plt.subplots(1, 2, figsize=(9.2, 3.6), sharey=True) for ax, metric in zip(axes, ["MCR", "MAC"]): colors = [OURS_COLOR, BASELINE_COLOR, BASELINE_COLOR] _bar_with_labels(ax, df["Configuration"].tolist(), df[metric].tolist(), colors) _style_axes(ax, f"RQ3 Ablation: {metric}", metric, (0.0, 1.0)) ax.tick_params(axis="x", rotation=10) _save(fig, "rq3_ablation_mcr_mac") def main() -> int: plt.rcParams.update( { "font.size": 11, "axes.titlesize": 12, "axes.labelsize": 11, "figure.dpi": 120, } ) plot_qna_accuracy() plot_model_building() plot_query_translation() plot_ablation() print(f"Wrote plots to: {PLOTS_DIR.resolve()}") return 0 if __name__ == "__main__": raise SystemExit(main())