Add policy_eval/src/policy_eval/core.py
This commit is contained in:
commit
ade86428c6
1 changed files with 69 additions and 0 deletions
69
policy_eval/src/policy_eval/core.py
Normal file
69
policy_eval/src/policy_eval/core.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
from __future__ import annotations
|
||||
import json
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass, asdict
|
||||
from typing import Any, Dict
|
||||
|
||||
|
||||
@dataclass
|
||||
class EvaluationMetrics:
|
||||
total_warn: int
|
||||
total_fail: int
|
||||
unknowns: int
|
||||
manual_overrides: int
|
||||
|
||||
def to_dict(self) -> Dict[str, int]:
|
||||
"""Return a dict with keys sorted alphabetically for diff-friendly output."""
|
||||
data = asdict(self)
|
||||
return {k: data[k] for k in sorted(data.keys())}
|
||||
|
||||
|
||||
def evaluate_policy(drift_report: Dict[str, Any]) -> Dict[str, int]:
|
||||
"""Aggregiert Metriken aus einem Drift-Report.
|
||||
|
||||
Erwartet ein Dictionary, das Metriken wie Warnungen, Fehler,
|
||||
unbekannte Elemente oder manuelle Overrides enthält.
|
||||
Fehlende Felder werden als 'unknown' gezählt.
|
||||
|
||||
Args:
|
||||
drift_report: Strukturierter JSON-Report über Policy-Drifts.
|
||||
|
||||
Returns:
|
||||
Dict mit aggregierten Metriken (total_warn, total_fail, unknowns, manual_overrides).
|
||||
"""
|
||||
if not isinstance(drift_report, Mapping):
|
||||
raise TypeError("drift_report must be a dict-like object")
|
||||
|
||||
# Extract known keys safely
|
||||
warn = drift_report.get("warn") or drift_report.get("warnings") or 0
|
||||
fail = drift_report.get("fail") or drift_report.get("errors") or 0
|
||||
overrides = drift_report.get("manual_overrides") or drift_report.get("overrides") or 0
|
||||
|
||||
# Identify unknowns: fields not in known keys and missing expected ones
|
||||
known_keys = {"warn", "warnings", "fail", "errors", "manual_overrides", "overrides"}
|
||||
unknown_fields = [k for k in drift_report.keys() if k not in known_keys]
|
||||
|
||||
# Missing expected keys are also counted as unknown
|
||||
missing_expected = [key for key in ("warn", "fail", "manual_overrides") if key not in drift_report]
|
||||
unknown_total = len(unknown_fields) + len(missing_expected)
|
||||
|
||||
# Ensure numeric consistency
|
||||
try:
|
||||
metrics = EvaluationMetrics(
|
||||
total_warn=int(warn),
|
||||
total_fail=int(fail),
|
||||
unknowns=int(unknown_total),
|
||||
manual_overrides=int(overrides)
|
||||
)
|
||||
except (ValueError, TypeError) as e:
|
||||
raise ValueError(f"Invalid field type in drift_report: {e}") from e
|
||||
|
||||
result = metrics.to_dict()
|
||||
|
||||
# Basic assertions for CI consistency
|
||||
assert all(isinstance(v, int) for v in result.values()), "All metric values must be int"
|
||||
assert all(k in {"manual_overrides", "total_fail", "total_warn", "unknowns"} for k in result), (
|
||||
"Unexpected keys in result dict"
|
||||
)
|
||||
|
||||
return result
|
||||
Loading…
Reference in a new issue