Spaces:
Paused
Paused
Download sensorflow/retro/policy.py from whassan/SensorflowStudio: direct link, hf CLI and curl.
- Browser
- Download file 12.6 kB
-
https://huggingface.co/spaces/whassan/SensorflowStudio/resolve/main/sensorflow/retro/policy.py
- Command line
-
hf download hf://spaces/whassan/SensorflowStudio/sensorflow/retro/policy.py
-
curl -L -o policy.py https://huggingface.co/spaces/whassan/SensorflowStudio/resolve/main/sensorflow/retro/policy.py
12.6 kB
| """Deterministic severity framework and launch-recommendation gate. | |
| This module OWNS the safety boundary. The LLM may propose a severity with | |
| evidence; this engine validates or overrides it, records divergence, and is | |
| the ONLY producer of launch determinations. | |
| Severity model (asymmetric FN/FP costs, context-dependent — deliberately NOT | |
| "every FN outranks every FP"): | |
| FALSE NEGATIVE cost ~ VRU weight x safety-relevant-distance factor x | |
| relative-motion factor x remaining-reaction-time factor | |
| FALSE POSITIVE cost ~ intervention magnitude x traffic disruption exposure | |
| A benign distant non-closing FN scores below a hard-braking phantom FP with | |
| a tailgater; the test suite pins this ordering. | |
| """ | |
| from __future__ import annotations | |
| from typing import Any, Dict, List, Optional, Tuple | |
| from pydantic import BaseModel, Field | |
| from sensorflow.retro.scorecard import LaunchRecommendation, Severity | |
| POLICY_VERSION = "retro-policy/1.0.0" | |
| # Versioned policy configuration: all thresholds live here, not in code paths. | |
| POLICY_CONFIG: Dict[str, Any] = { | |
| "version": POLICY_VERSION, | |
| "vru_weights": { | |
| "pedestrian": 1.0, "cyclist": 0.95, "motorcycle": 0.9, | |
| "wheelchair": 1.0, "vehicle": 0.6, "truck": 0.65, "bus": 0.65, | |
| "unknown": 0.5, "debris": 0.15, "plastic_bag": 0.05, "static": 0.2, | |
| }, | |
| "fn_severity_cutoffs": {"critical": 0.45, "disruptive": 0.18}, | |
| "fp_severity_cutoffs": {"critical": 0.55, "disruptive": 0.28}, | |
| "hard_brake_g": 0.35, # from SFS-SAFE-001 REQ-FP-02 [SYNTHETIC] | |
| "tailgater_gap_s": 1.5, | |
| "scr_regression_tolerance": 0.002, # 0.2 pp, SFS-LAUNCH-004 GATE-02 [SYNTHETIC] | |
| "collision_risk_ttc_s": 1.5, | |
| } | |
| class SeverityContext(BaseModel): | |
| """Deterministic inputs to the severity computation (facts + derived).""" | |
| failure_type: str # FALSE_NEGATIVE | FALSE_POSITIVE | ... | |
| object_class: Optional[str] = None # ground-truth class | |
| distance_m: Optional[float] = None | |
| stopping_distance_m: Optional[float] = None | |
| closing_velocity_mps: Optional[float] = None | |
| ttc_s: Optional[float] = None | |
| total_reaction_time_s: Optional[float] = None | |
| collision_occurred: bool = False | |
| near_miss: bool = False | |
| intervention_decel_mps2: Optional[float] = None # observed planner decel | |
| following_gap_s: Optional[float] = None # rear traffic time gap | |
| ego_speed_mps: Optional[float] = None | |
| class SeverityResult(BaseModel): | |
| severity: Severity | |
| score: float | |
| rule_trace: List[str] = Field(default_factory=list) | |
| computable: bool = True | |
| def _fn_severity(ctx: SeverityContext, cfg: Dict[str, Any], | |
| trace: List[str]) -> Tuple[Severity, float]: | |
| w = cfg["vru_weights"].get((ctx.object_class or "unknown").lower(), 0.5) | |
| trace.append(f"FN branch: class '{ctx.object_class}' weight={w}") | |
| if ctx.distance_m is not None and ctx.stopping_distance_m: | |
| ratio = ctx.distance_m / ctx.stopping_distance_m | |
| if ratio <= 1.0: | |
| dist_f = 1.0 | |
| elif ratio <= 1.5: | |
| dist_f = 0.6 | |
| elif ratio <= 3.0: | |
| dist_f = 0.25 | |
| else: | |
| dist_f = 0.05 | |
| trace.append(f"distance {ctx.distance_m:.1f} m = {ratio:.2f}x stopping " | |
| f"distance -> factor {dist_f}") | |
| else: | |
| dist_f = 0.5 | |
| trace.append("distance vs stopping distance not computable -> neutral 0.5") | |
| closing = ctx.closing_velocity_mps or 0.0 | |
| if closing > 0: | |
| motion_f = min(1.0, 0.3 + closing / 15.0) | |
| trace.append(f"closing at {closing:.1f} m/s -> motion factor {motion_f:.2f}") | |
| else: | |
| motion_f = 0.1 | |
| trace.append("not closing -> motion factor 0.1") | |
| if ctx.ttc_s is not None and ctx.total_reaction_time_s is not None: | |
| remaining = ctx.ttc_s - ctx.total_reaction_time_s | |
| if remaining <= 0: | |
| rrt_f = 1.0 | |
| elif remaining < 1.0: | |
| rrt_f = 0.8 | |
| elif remaining < 2.5: | |
| rrt_f = 0.5 | |
| else: | |
| rrt_f = 0.2 | |
| trace.append(f"remaining reaction time {remaining:.2f} s -> factor {rrt_f}") | |
| elif closing > 0: | |
| rrt_f = 0.6 | |
| trace.append("TTC/reaction budget unknown while closing -> conservative 0.6") | |
| else: | |
| rrt_f = 0.1 | |
| trace.append("no predicted collision path -> reaction factor 0.1") | |
| score = w * dist_f * (0.5 * motion_f + 0.5 * rrt_f) | |
| cuts = cfg["fn_severity_cutoffs"] | |
| if ctx.collision_occurred: | |
| trace.append("collision occurred -> FATAL override") | |
| return Severity.FATAL, max(score, 1.0) | |
| if score >= cuts["critical"] or (ctx.near_miss and score >= cuts["disruptive"]): | |
| return Severity.CRITICAL, score | |
| if score >= cuts["disruptive"]: | |
| return Severity.DISRUPTIVE, score | |
| if ctx.near_miss: | |
| trace.append("near-miss recorded despite low computed score -> " | |
| "DISRUPTIVE floor") | |
| return Severity.DISRUPTIVE, score | |
| return Severity.BENIGN, score | |
| def _fp_severity(ctx: SeverityContext, cfg: Dict[str, Any], | |
| trace: List[str]) -> Tuple[Severity, float]: | |
| decel = ctx.intervention_decel_mps2 or 0.0 | |
| intervention_f = min(1.0, decel / 9.0) | |
| trace.append(f"FP branch: intervention decel {decel:.1f} m/s^2 " | |
| f"({decel / 9.80665:.2f} g) -> factor {intervention_f:.2f}") | |
| if ctx.following_gap_s is not None: | |
| if ctx.following_gap_s < 1.0: | |
| disruption_f = 1.0 | |
| elif ctx.following_gap_s < cfg["tailgater_gap_s"]: | |
| disruption_f = 0.8 | |
| elif ctx.following_gap_s < 3.0: | |
| disruption_f = 0.5 | |
| else: | |
| disruption_f = 0.3 | |
| trace.append(f"following gap {ctx.following_gap_s:.1f} s -> " | |
| f"disruption factor {disruption_f}") | |
| else: | |
| disruption_f = 0.4 | |
| trace.append("following traffic unknown -> disruption factor 0.4 (assumed)") | |
| speed_f = min(1.0, (ctx.ego_speed_mps or 0.0) / 30.0) | |
| trace.append(f"ego speed factor {speed_f:.2f}") | |
| score = intervention_f * (0.55 + 0.45 * disruption_f) * (0.6 + 0.4 * speed_f) | |
| cuts = cfg["fp_severity_cutoffs"] | |
| hard_brake = decel >= cfg["hard_brake_g"] * 9.80665 | |
| tailgated = (ctx.following_gap_s is not None | |
| and ctx.following_gap_s < cfg["tailgater_gap_s"]) | |
| if ctx.collision_occurred: | |
| trace.append("collision occurred (induced) -> FATAL override") | |
| return Severity.FATAL, max(score, 1.0) | |
| if hard_brake and tailgated: | |
| trace.append(f"hard brake (>= {cfg['hard_brake_g']} g) with rear gap " | |
| f"< {cfg['tailgater_gap_s']} s -> CRITICAL rule " | |
| "(REQ-FP-02 [SYNTHETIC])") | |
| return Severity.CRITICAL, max(score, cuts["critical"]) | |
| if score >= cuts["critical"]: | |
| return Severity.CRITICAL, score | |
| if score >= cuts["disruptive"]: | |
| return Severity.DISRUPTIVE, score | |
| return Severity.BENIGN, score | |
| def compute_severity(ctx: SeverityContext, | |
| config: Optional[Dict[str, Any]] = None) -> SeverityResult: | |
| """Deterministic severity from contextual evidence. Pure function.""" | |
| cfg = config or POLICY_CONFIG | |
| trace: List[str] = [f"policy {cfg['version']}"] | |
| ft = ctx.failure_type.upper() | |
| if ft in ("FALSE_NEGATIVE", "MISSED_DETECTION", "LATE_DETECTION"): | |
| sev, score = _fn_severity(ctx, cfg, trace) | |
| elif ft in ("FALSE_POSITIVE", "PHANTOM_OBJECT", "MISCLASSIFICATION_FP"): | |
| sev, score = _fp_severity(ctx, cfg, trace) | |
| elif ft == "MISCLASSIFICATION": | |
| # Consequence-dominant: score both directions, take the worse. | |
| t1: List[str] = [] | |
| t2: List[str] = [] | |
| s1 = _fn_severity(ctx, cfg, t1) | |
| s2 = _fp_severity(ctx, cfg, t2) | |
| order = [Severity.BENIGN, Severity.DISRUPTIVE, Severity.CRITICAL, Severity.FATAL] | |
| if order.index(s1[0]) >= order.index(s2[0]): | |
| sev, score = s1 | |
| trace += ["misclassification scored on FN consequence branch"] + t1 | |
| else: | |
| sev, score = s2 | |
| trace += ["misclassification scored on FP consequence branch"] + t2 | |
| else: | |
| trace.append(f"failure type '{ctx.failure_type}' has no severity model " | |
| "-> not computable, conservative DISRUPTIVE floor") | |
| return SeverityResult(severity=Severity.DISRUPTIVE, score=0.0, | |
| rule_trace=trace, computable=False) | |
| return SeverityResult(severity=sev, score=round(score, 4), rule_trace=trace) | |
| SEVERITY_ORDER = [Severity.BENIGN, Severity.DISRUPTIVE, Severity.CRITICAL, | |
| Severity.FATAL] | |
| class SeverityAdjudication(BaseModel): | |
| policy_severity: Severity | |
| ai_proposed_severity: Optional[Severity] | |
| final_severity: Severity # always the policy severity | |
| divergence: bool | |
| divergence_note: str = "" | |
| def adjudicate_severity(policy: SeverityResult, | |
| ai_proposed: Optional[Severity]) -> SeverityAdjudication: | |
| """Policy validates/overrides the LLM proposal; divergence is recorded | |
| and (by the caller) flagged for human review.""" | |
| divergence = ai_proposed is not None and ai_proposed != policy.severity | |
| note = "" | |
| if divergence: | |
| direction = ("understated" if SEVERITY_ORDER.index(ai_proposed) | |
| < SEVERITY_ORDER.index(policy.severity) else "overstated") | |
| note = (f"AI proposed {ai_proposed.value} but deterministic policy " | |
| f"computed {policy.severity.value} (AI {direction} severity); " | |
| "policy severity is authoritative and the divergence requires " | |
| "human review") | |
| return SeverityAdjudication(policy_severity=policy.severity, | |
| ai_proposed_severity=ai_proposed, | |
| final_severity=policy.severity, | |
| divergence=divergence, divergence_note=note) | |
| class LaunchDecision(BaseModel): | |
| recommendation: LaunchRecommendation | |
| rationale: List[str] | |
| policy_version: str = POLICY_VERSION | |
| def launch_gate(severity: Severity, | |
| has_evidence_gaps: bool, | |
| scr_impact: Optional[float], | |
| scr_significant: Optional[bool], | |
| config: Optional[Dict[str, Any]] = None) -> LaunchDecision: | |
| """Deterministic launch recommendation. INSUFFICIENT_EVIDENCE never | |
| becomes PASS (SFS-LAUNCH-004 GATE-03 [SYNTHETIC], enforced here).""" | |
| cfg = config or POLICY_CONFIG | |
| rationale: List[str] = [f"policy {cfg['version']}"] | |
| if has_evidence_gaps: | |
| rationale.append("required evidence fields are UNKNOWN -> " | |
| "INSUFFICIENT_EVIDENCE (can never be converted to PASS; " | |
| "resolve evidence gaps and re-run)") | |
| return LaunchDecision(recommendation=LaunchRecommendation.INSUFFICIENT_EVIDENCE, | |
| rationale=rationale) | |
| if severity in (Severity.CRITICAL, Severity.FATAL): | |
| rationale.append(f"unresolved {severity.value} severity -> FAIL (GATE-01)") | |
| return LaunchDecision(recommendation=LaunchRecommendation.FAIL, | |
| rationale=rationale) | |
| tol = cfg["scr_regression_tolerance"] | |
| if scr_impact is not None and scr_impact < -tol and scr_significant: | |
| rationale.append(f"safety-critical recall regressed {scr_impact:+.4f} " | |
| f"beyond tolerance {tol} with statistical significance " | |
| "-> FAIL (GATE-02)") | |
| return LaunchDecision(recommendation=LaunchRecommendation.FAIL, | |
| rationale=rationale) | |
| if severity == Severity.DISRUPTIVE: | |
| rationale.append("DISRUPTIVE severity -> CONDITIONAL_PASS pending " | |
| "mitigation tracking") | |
| return LaunchDecision(recommendation=LaunchRecommendation.CONDITIONAL_PASS, | |
| rationale=rationale) | |
| if scr_impact is not None and scr_impact < -tol and scr_significant is None: | |
| rationale.append(f"SCR delta {scr_impact:+.4f} beyond tolerance but " | |
| "significance not established -> CONDITIONAL_PASS " | |
| "pending seqeval confirmation") | |
| return LaunchDecision(recommendation=LaunchRecommendation.CONDITIONAL_PASS, | |
| rationale=rationale) | |
| rationale.append("BENIGN severity, no significant safety-critical recall " | |
| "regression -> PASS") | |
| return LaunchDecision(recommendation=LaunchRecommendation.PASS, | |
| rationale=rationale) | |