"""Evaluation figures for the model report and dashboard.""" from __future__ import annotations import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt import numpy as np from sklearn.metrics import precision_recall_curve, roc_curve from src import config PRIMARY = "#1f4e79" ACCENT = "#c0392b" def make_figures(y_true, prob, pred, threshold) -> None: config.FIGURES_DIR.mkdir(parents=True, exist_ok=True) # PR curve prec, rec, _ = precision_recall_curve(y_true, prob) fig, ax = plt.subplots(figsize=(5, 4)) ax.plot(rec, prec, color=PRIMARY, lw=2) ax.set_xlabel("Recall"); ax.set_ylabel("Precision"); ax.set_title("Precision–Recall") ax.grid(alpha=0.3); fig.tight_layout() fig.savefig(config.FIGURES_DIR / "pr_curve.png", dpi=130); plt.close(fig) # ROC curve fpr, tpr, _ = roc_curve(y_true, prob) fig, ax = plt.subplots(figsize=(5, 4)) ax.plot(fpr, tpr, color=PRIMARY, lw=2) ax.plot([0, 1], [0, 1], "--", color="gray", lw=1) ax.set_xlabel("False Positive Rate"); ax.set_ylabel("True Positive Rate"); ax.set_title("ROC") ax.grid(alpha=0.3); fig.tight_layout() fig.savefig(config.FIGURES_DIR / "roc_curve.png", dpi=130); plt.close(fig) # Confusion matrix from sklearn.metrics import confusion_matrix cm = confusion_matrix(y_true, pred) fig, ax = plt.subplots(figsize=(4, 3.5)) im = ax.imshow(cm, cmap="Blues") for (i, j), v in np.ndenumerate(cm): ax.text(j, i, str(v), ha="center", va="center", color="white" if v > cm.max() / 2 else "black", fontsize=12) ax.set_xticks([0, 1]); ax.set_yticks([0, 1]) ax.set_xticklabels(["Legit", "Mule"]); ax.set_yticklabels(["Legit", "Mule"]) ax.set_xlabel("Predicted"); ax.set_ylabel("Actual"); ax.set_title("Confusion (test)") fig.tight_layout(); fig.savefig(config.FIGURES_DIR / "confusion_matrix.png", dpi=130); plt.close(fig) # Risk score distribution (threshold-anchored: 50 == decision threshold) score = np.array([config.prob_to_risk(p, threshold) for p in prob]) fig, ax = plt.subplots(figsize=(6, 4)) ax.hist(score[y_true == 0], bins=40, alpha=0.6, label="Legit", color=PRIMARY, density=True) ax.hist(score[y_true == 1], bins=40, alpha=0.7, label="Mule", color=ACCENT, density=True) ax.axvline(50, color="black", ls="--", lw=1.5, label="Decision threshold (50)") ax.set_xlabel("Risk score (0–100)"); ax.set_ylabel("Density") ax.set_title("Risk score distribution by class"); ax.legend() fig.tight_layout(); fig.savefig(config.FIGURES_DIR / "risk_distribution.png", dpi=130); plt.close(fig)