Add policy_eval_script/src/policy_eval_script/core.py
This commit is contained in:
commit
e187b0b3b0
1 changed files with 87 additions and 0 deletions
87
policy_eval_script/src/policy_eval_script/core.py
Normal file
87
policy_eval_script/src/policy_eval_script/core.py
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
import json
|
||||||
|
import hashlib
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, Any
|
||||||
|
|
||||||
|
|
||||||
|
class EvaluationResult(dict):
|
||||||
|
"""Container für das Evaluationsergebnis mit festen Feldern."""
|
||||||
|
|
||||||
|
REQUIRED_FIELDS = {"stratum", "decision", "reason", "policy_hash"}
|
||||||
|
|
||||||
|
def __init__(self, stratum: str, decision: str, reason: str, policy_hash: str):
|
||||||
|
super().__init__(
|
||||||
|
stratum=stratum,
|
||||||
|
decision=decision,
|
||||||
|
reason=reason,
|
||||||
|
policy_hash=policy_hash,
|
||||||
|
)
|
||||||
|
self._validate()
|
||||||
|
|
||||||
|
def _validate(self) -> None:
|
||||||
|
assert set(self.keys()) == self.REQUIRED_FIELDS, (
|
||||||
|
f"EvaluationResult muss Felder {self.REQUIRED_FIELDS} enthalten"
|
||||||
|
)
|
||||||
|
for k, v in self.items():
|
||||||
|
if not isinstance(v, str):
|
||||||
|
raise TypeError(f"Feld {k} muss ein String sein, erhielt {type(v)}")
|
||||||
|
|
||||||
|
|
||||||
|
def _compute_policy_hash(policy_constants: Dict[str, Any]) -> str:
|
||||||
|
"""Berechnet einen Hash über den Inhalt der Policy-Konstanten."""
|
||||||
|
encoded = json.dumps(policy_constants, sort_keys=True).encode()
|
||||||
|
return hashlib.sha256(encoded).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_drift_report(drift_report: Dict[str, Any]) -> None:
|
||||||
|
if not isinstance(drift_report, dict):
|
||||||
|
raise TypeError("drift_report muss ein Dictionary sein")
|
||||||
|
if not drift_report:
|
||||||
|
raise ValueError("drift_report darf nicht leer sein")
|
||||||
|
|
||||||
|
|
||||||
|
def evaluate_policy(drift_report: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Berechnet ein Policy-Evaluationsergebnis für einen gegebenen Drift-Report."""
|
||||||
|
_validate_drift_report(drift_report)
|
||||||
|
|
||||||
|
constants_path = Path("policy_constants.json")
|
||||||
|
if not constants_path.exists():
|
||||||
|
raise FileNotFoundError("policy_constants.json nicht gefunden")
|
||||||
|
|
||||||
|
with constants_path.open("r", encoding="utf-8") as f:
|
||||||
|
policy_constants = json.load(f)
|
||||||
|
|
||||||
|
policy_hash = _compute_policy_hash(policy_constants)
|
||||||
|
|
||||||
|
results = []
|
||||||
|
thresholds = policy_constants.get("thresholds", {})
|
||||||
|
default_threshold = thresholds.get("default", 0.1)
|
||||||
|
|
||||||
|
for stratum, metrics in drift_report.items():
|
||||||
|
if not isinstance(metrics, dict):
|
||||||
|
raise TypeError(f"Metrics für {stratum} muss ein Dictionary sein")
|
||||||
|
max_drift = max(metrics.values()) if metrics else 0.0
|
||||||
|
threshold = thresholds.get(stratum, default_threshold)
|
||||||
|
|
||||||
|
if max_drift > 2 * threshold:
|
||||||
|
decision = "FAIL"
|
||||||
|
reason = f"Drift {max_drift:.3f} über doppeltem Grenzwert {threshold:.3f}"
|
||||||
|
elif max_drift > threshold:
|
||||||
|
decision = "WARN"
|
||||||
|
reason = f"Drift {max_drift:.3f} über Grenzwert {threshold:.3f}"
|
||||||
|
else:
|
||||||
|
decision = "PASS"
|
||||||
|
reason = f"Drift {max_drift:.3f} innerhalb Grenzwert {threshold:.3f}"
|
||||||
|
|
||||||
|
result = EvaluationResult(
|
||||||
|
stratum=stratum,
|
||||||
|
decision=decision,
|
||||||
|
reason=reason,
|
||||||
|
policy_hash=policy_hash,
|
||||||
|
)
|
||||||
|
results.append(result)
|
||||||
|
|
||||||
|
# Wenn nur ein Stratum, gebe das eine Dict zurück, sonst Kopie für alle.
|
||||||
|
if len(results) == 1:
|
||||||
|
return results[0]
|
||||||
|
return {r["stratum"]: dict(r) for r in results}
|
||||||
Loading…
Reference in a new issue