#!/usr/bin/env python3 """Shared, dependency-free helpers for v25 evaluation tools.""" from __future__ import annotations import json import math from pathlib import Path from typing import Any, Iterable NOT_MEASURED = "not measured" def read_json(path: Path) -> Any: with path.open(encoding="utf-8") as handle: return json.load(handle) def read_jsonl(path: Path) -> list[dict[str, Any]]: rows = [] with path.open(encoding="utf-8") as handle: for line_number, line in enumerate(handle, 1): if not line.strip(): continue value = json.loads(line) if not isinstance(value, dict): raise ValueError(f"{path}:{line_number}: expected a JSON object") rows.append(value) return rows def write_json(path: Path, value: Any) -> None: path.parent.mkdir(parents=True, exist_ok=True) temporary = path.with_suffix(path.suffix + ".tmp") temporary.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="utf-8") temporary.replace(path) def finite_number(value: Any) -> float | None: if isinstance(value, bool) or not isinstance(value, (int, float)): return None number = float(value) return number if math.isfinite(number) else None def mean(values: Iterable[float]) -> float | None: items = list(values) return sum(items) / len(items) if items else None def percentile(values: Iterable[float], fraction: float) -> float | None: items = sorted(values) if not items: return None index = (len(items) - 1) * fraction low = int(index) high = min(low + 1, len(items) - 1) return items[low] + (items[high] - items[low]) * (index - low)