from __future__ import annotations import base64 import io import json import re import subprocess import unicodedata from datetime import datetime, timezone from pathlib import Path from typing import Any import matplotlib.pyplot as plt def slugify(text: str) -> str: normalized = unicodedata.normalize("NFKD", text).encode("ascii", "ignore").decode("ascii") return re.sub(r"_+", "_", re.sub(r"[^A-Za-z0-9]+", "_", normalized.strip())).strip("_") def fig_to_b64(fig) -> str: buffer = io.BytesIO() fig.savefig(buffer, format="png", dpi=170, bbox_inches="tight", facecolor=fig.get_facecolor()) plt.close(fig) buffer.seek(0) return base64.b64encode(buffer.read()).decode("ascii") def utc_now_iso() -> str: return datetime.now(timezone.utc).replace(microsecond=0).isoformat() def git_commit(repo_root: Path) -> str | None: try: out = subprocess.check_output( ["git", "rev-parse", "HEAD"], cwd=repo_root, text=True, stderr=subprocess.DEVNULL, ).strip() return out except Exception: return None def write_json(path: Path, payload: dict[str, Any]) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")