File size: 8,660 Bytes
6342806
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
"""Pydantic schemas shared by the nextgen evaluation platform.

Every datum that can appear in a report carries a DataLabel; the label is
assigned at creation time and is never inferred downstream.

AgenticSafetyScorecard: Feature 1 (sensorflow.agentic) is being built
concurrently by another workstream. We import its scorecard when available so
the two subsystems share one shape; otherwise a compatible local definition
(the fields relevant to Features 2-5: behavioral_impact,
counterfactual_validated, closed_loop_validated) is used and marked as such.
"""

from __future__ import annotations

from datetime import datetime, timezone
from enum import Enum
from typing import Any, Dict, List, Optional

from pydantic import BaseModel, Field


def utc_now() -> str:
    return datetime.now(timezone.utc).isoformat()


class DataLabel(str, Enum):
    """Provenance of a datum. Simulation is evidence, not reality; the label
    says which kind of evidence and travels with every report."""

    REAL = "REAL"                      # captured on-vehicle
    REPLAYED = "REPLAYED"              # real log replayed open-loop
    SIMULATED = "SIMULATED"            # synthetic scenario, nominal params
    GENERATED = "GENERATED"            # model-generated content
    COUNTERFACTUAL = "COUNTERFACTUAL"  # transformation of a source scenario


class TransformationStep(BaseModel):
    """One step of a counterfactual recipe (deterministic, parameterized)."""

    kind: str                  # e.g. "environment.day_to_night"
    params: Dict[str, Any] = Field(default_factory=dict)


class Provenance(BaseModel):
    """Where a counterfactual scenario came from — carried through every
    evaluation report that touches the scenario."""

    source_scene_id: str
    recipe: List[TransformationStep] = Field(default_factory=list)
    seed: int = 0
    generator: str = "nextgen.worldmodel.DeterministicSceneTransformer"
    generator_version: str = "1.0"
    data_label: DataLabel = DataLabel.COUNTERFACTUAL
    created_at: str = Field(default_factory=utc_now)


class ValidityReport(BaseModel):
    """Output of the counterfactual validity gate (validity.py)."""

    scenario_id: str
    checks: List[Dict[str, Any]] = Field(default_factory=list)
    simulation_fidelity_score: float = 0.0   # [0,1] physical/temporal/sensor
    counterfactual_validity: float = 0.0     # [0,1] gate-weighted composite
    realism_confidence: float = 0.0          # [0,1] distribution similarity
    accepted: bool = False
    evaluation_weight: float = 0.0           # cap applied for low fidelity
    weight_capped: bool = False
    reasons: List[str] = Field(default_factory=list)


class CounterfactualScenario(BaseModel):
    scenario_id: str
    provenance: Provenance
    n_frames: int = 0
    n_actors: int = 0
    environment: Dict[str, str] = Field(default_factory=dict)
    validity: Optional[ValidityReport] = None


class LineageRecord(BaseModel):
    """Full reproducibility record for one evaluation run. A run whose lineage
    is incomplete is marked INVALID for launch purposes (lineage.py)."""

    model_version: Optional[str] = None
    baseline_version: Optional[str] = None
    dataset_version: Optional[str] = None
    scenario_set_version: Optional[str] = None
    sensor_sim_version: Optional[str] = None
    simulation_version: Optional[str] = None
    feature_pipeline_version: Optional[str] = None
    metric_version: Optional[str] = None
    policy_version: Optional[str] = None
    seeds: Dict[str, int] = Field(default_factory=dict)
    created_at: str = Field(default_factory=utc_now)


class EvaluationRun(BaseModel):
    run_id: str
    kind: str  # counterfactual_generation | closed_loop | causal_replay | gauntlet | safety_report
    status: str = "created"  # created | running | completed | halted | failed
    data_labels: List[DataLabel] = Field(default_factory=list)
    lineage: Optional[LineageRecord] = None
    lineage_valid: bool = False
    valid_for_launch: bool = False
    invalid_reasons: List[str] = Field(default_factory=list)
    created_at: str = Field(default_factory=utc_now)
    params: Dict[str, Any] = Field(default_factory=dict)
    results: Dict[str, Any] = Field(default_factory=dict)


class BehavioralMetrics(BaseModel):
    """Closed-loop behavioral metrics for one scenario run (closedloop.py).
    All safety-region math reuses sensorflow.safety.ssam_ext."""

    detection_latency_s: Optional[float] = None   # first-relevant-object detect delay
    time_to_detection_s: Optional[float] = None   # sim time of first detection
    min_ttc_s: Optional[float] = None
    stopping_distance_m: Optional[float] = None
    max_deceleration_mps2: float = 0.0
    max_steering_rate_radps: float = 0.0
    planner_interventions: int = 0
    collision: bool = False
    collision_probability: float = 0.0
    min_separation_m: Optional[float] = None
    safety_margin_m: Optional[float] = None       # min_separation - required stopping gap
    final_speed_mps: float = 0.0


class BehavioralAssessment(BaseModel):
    scenario_id: str
    data_label: DataLabel
    perception_mode: str  # "actual" | "corrected"
    metrics: BehavioralMetrics
    open_loop: Dict[str, Any] = Field(default_factory=dict)   # complementary, never replaced
    trajectory: List[Dict[str, float]] = Field(default_factory=list)


CAUSAL_METRIC_ONLY = "METRIC_ONLY"
CAUSAL_BEHAVIORAL = "BEHAVIORALLY_CONSEQUENTIAL"


class CausalReplayResult(BaseModel):
    scenario_id: str
    data_label: DataLabel
    actual: BehavioralAssessment
    corrected: BehavioralAssessment
    diffs: Dict[str, Any] = Field(default_factory=dict)
    causal_chain: List[Dict[str, Any]] = Field(default_factory=list)
    verdict: str = CAUSAL_METRIC_ONLY


class DistributionShiftAssessment(BaseModel):
    run_id: str
    method: str
    shifts: List[Dict[str, Any]] = Field(default_factory=list)
    psi: Optional[float] = None
    js_divergence: Optional[float] = None
    magnitude: Optional[str] = None
    data_labels: List[DataLabel] = Field(default_factory=list)


class ComputeOptimizationResult(BaseModel):
    report_id: str
    n_scenarios: int
    n_models: int
    naive_full_inferences: int
    optimized_backbone_computes: int
    optimized_head_computes: int
    cache_hits: int
    cache_misses: int
    hit_rate: float
    naive_cost_s: float          # measured per-unit timings x naive counts
    optimized_cost_s: float      # actually measured wall time
    savings_ratio: float
    measured_backbone_s: float
    measured_head_s: float
    invalidation: str
    created_at: str = Field(default_factory=utc_now)


class LaunchRecommendation(BaseModel):
    run_id: str
    recommendation: str  # LAUNCH | DO_NOT_LAUNCH | INSUFFICIENT_EVIDENCE | INVALID
    blockers: List[str] = Field(default_factory=list)
    statistical_significance: Dict[str, Any] = Field(default_factory=dict)
    safety_significance: Dict[str, Any] = Field(default_factory=dict)  # never equated with statistical
    lineage_valid: bool = False
    data_labels: List[DataLabel] = Field(default_factory=list)
    policy_version: str = "nextgen-launch-policy-1.0"


# --------------------------------------------------------------- scorecard

try:  # Feature 1 is built concurrently in sensorflow/agentic (guarded import).
    from sensorflow.agentic.models import AgenticSafetyScorecard  # type: ignore # noqa: F401
    AGENTIC_SCORECARD_SOURCE = "sensorflow.agentic.models"
except Exception:  # pragma: no cover - depends on sibling workstream timing
    AgenticSafetyScorecard = None  # type: ignore[assignment]
    AGENTIC_SCORECARD_SOURCE = "unavailable (sensorflow.agentic not importable)"


class ScorecardBehavioralExtension(BaseModel):
    """The Features-2-5 contribution to the shared safety scorecard.

    The agentic package's AgenticSafetyScorecard (severity / exposure /
    policy_outcome / ...) does not itself carry closed-loop fields; this
    extension composes with it via agentic_scorecard_id rather than
    redefining it. behavioral_impact carries the causal-replay verdict
    (METRIC_ONLY / BEHAVIORALLY_CONSEQUENTIAL); the two *_validated flags say
    whether counterfactual validity gating and closed-loop replay were
    actually run for the case.
    """

    case_id: str
    agentic_scorecard_id: Optional[str] = None  # link to Feature 1's scorecard
    behavioral_impact: Optional[str] = None     # causal replay verdict
    counterfactual_validated: Optional[bool] = None
    closed_loop_validated: Optional[bool] = None
    data_label: DataLabel = DataLabel.SIMULATED
    details: Dict[str, Any] = Field(default_factory=dict)