← Files RAGOpsARCHIVED FILE
skills/evaluate-ai-release/scripts/vendor/ragops/usage.py
4.79 KB · Oct 2, 2026 · 00:29 UTC
from __future__ import annotations
import json
import re
from collections import Counter
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
EVENT_VERSION = "ragops-local-usage-event-0.1"
REPORT_VERSION = "ragops-local-usage-report-0.1"
EVENT_FIELDS = {
"schema_version",
"recorded_at",
"ragops_version",
"command",
"exit_code",
}
COMMAND = re.compile(r"[a-z][a-z0-9-]{0,63}")
class UsageContractError(ValueError):
"""Raised when local usage evidence violates its public contract."""
def _timestamp(value: Any, label: str) -> str:
if not isinstance(value, str) or not value.endswith("Z"):
raise UsageContractError(f"{label} must be a UTC timestamp ending in Z")
try:
parsed = datetime.fromisoformat(value.removesuffix("Z") + "+00:00")
except ValueError as exc:
raise UsageContractError(f"{label} must be an ISO timestamp") from exc
if parsed.tzinfo != UTC:
raise UsageContractError(f"{label} must use UTC")
return value
def _timestamp_sort_key(value: str) -> datetime:
return datetime.fromisoformat(value.removesuffix("Z") + "+00:00")
def _event(data: Any, line_number: int) -> dict[str, Any]:
if not isinstance(data, dict):
raise UsageContractError(f"usage event line {line_number} must be an object")
missing = EVENT_FIELDS - data.keys()
unexpected = data.keys() - EVENT_FIELDS
if missing:
raise UsageContractError(
f"usage event line {line_number} is missing fields: {', '.join(sorted(missing))}"
)
if unexpected:
raise UsageContractError(
f"usage event line {line_number} has unexpected fields: "
f"{', '.join(sorted(unexpected))}"
)
if data["schema_version"] != EVENT_VERSION:
raise UsageContractError(
f"usage event line {line_number} has unsupported schema_version"
)
recorded_at = _timestamp(data["recorded_at"], f"usage event line {line_number} recorded_at")
version = data["ragops_version"]
if not isinstance(version, str) or not version or len(version) > 64:
raise UsageContractError(
f"usage event line {line_number} ragops_version must be a non-empty string"
)
command = data["command"]
if not isinstance(command, str) or not COMMAND.fullmatch(command):
raise UsageContractError(f"usage event line {line_number} command is invalid")
exit_code = data["exit_code"]
if isinstance(exit_code, bool) or not isinstance(exit_code, int) or not 0 <= exit_code <= 255:
raise UsageContractError(
f"usage event line {line_number} exit_code must be an integer from 0 to 255"
)
return {
"schema_version": EVENT_VERSION,
"recorded_at": recorded_at,
"ragops_version": version,
"command": command,
"exit_code": exit_code,
}
def record_local_usage(
path: str | Path,
*,
ragops_version: str,
command: str,
exit_code: int,
recorded_at: str | None = None,
) -> None:
event = _event(
{
"schema_version": EVENT_VERSION,
"recorded_at": recorded_at
or datetime.now(UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z"),
"ragops_version": ragops_version,
"command": command,
"exit_code": exit_code,
},
1,
)
with Path(path).open("a", encoding="utf-8") as stream:
stream.write(json.dumps(event, ensure_ascii=False, sort_keys=True, separators=(",", ":")))
stream.write("\n")
def summarize_local_usage(path: str | Path) -> dict[str, Any]:
try:
lines = Path(path).read_text(encoding="utf-8").splitlines()
except (OSError, UnicodeDecodeError) as exc:
raise UsageContractError("usage events must be readable UTF-8 JSONL") from exc
events = []
for line_number, line in enumerate(lines, 1):
if not line.strip():
continue
try:
events.append(_event(json.loads(line), line_number))
except json.JSONDecodeError as exc:
raise UsageContractError(
f"usage event line {line_number} is not valid JSON"
) from exc
if not events:
raise UsageContractError("usage events must not be empty")
command_counts = Counter(event["command"] for event in events)
exit_code_counts = Counter(str(event["exit_code"]) for event in events)
timestamps = [event["recorded_at"] for event in events]
return {
"report_version": REPORT_VERSION,
"event_count": len(events),
"first_recorded_at": min(timestamps, key=_timestamp_sort_key),
"last_recorded_at": max(timestamps, key=_timestamp_sort_key),
"command_counts": dict(sorted(command_counts.items())),
"exit_code_counts": dict(sorted(exit_code_counts.items())),
}
SHA-256: 602bbc499117f573553645e74aa10381a9330886119874d34e970bcf9dc5efe9