File size: 2,681 Bytes
4b5fa98
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Counterfactual consequence: cosmetic violations classify as
NO_MATERIAL_CONSEQUENCE; a missed crossing pedestrian is SAFETY_CRITICAL;
every verdict carries measured evidence from both branches."""

from __future__ import annotations

import pytest

from sensorflow.rotr.attribution import attribute
from sensorflow.rotr.consequence import classify


@pytest.fixture(scope="module")
def classified(bank_v1, detections_v1):
    out = {}
    for sc in bank_v1:
        for v in detections_v1[sc.scenario_id]:
            attr = attribute(sc, v)
            out[v.violation_id] = (sc, v, attr, classify(sc, v, attr))
    return out


def _by_kind_cause(classified, kind, cause):
    return [t for t in classified.values()
            if t[0].planted.kind == kind and t[0].planted.cause_layer == cause]


class TestClasses:
    def test_missed_crossing_pedestrian_is_safety_critical(self, classified):
        cases = _by_kind_cause(classified, "fail_yield_pedestrian", "perception")
        assert cases
        for sc, v, attr, cf in cases:
            assert cf.consequence_class == "SAFETY_CRITICAL"
            assert cf.observed_safety.min_clearance_m is not None
            assert cf.observed_safety.min_clearance_m < 1.5

    def test_cosmetic_wrong_lane_is_no_material_consequence(self, classified):
        cases = _by_kind_cause(classified, "wrong_lane_association",
                               "localization")
        assert cases
        for sc, v, attr, cf in cases:
            assert cf.consequence_class == "NO_MATERIAL_CONSEQUENCE"
            assert cf.planner_evaluation.max_position_divergence_m < 2.0

    def test_control_fault_replays_the_plan(self, classified):
        cases = _by_kind_cause(classified, "stop_overshoot", "control")
        assert cases
        for sc, v, attr, cf in cases:
            assert cf.planner_evaluation.engine == "planned-trajectory"
            assert cf.consequence_class != "NO_MATERIAL_CONSEQUENCE"


class TestEvidence:
    def test_both_branches_measured(self, classified):
        for sc, v, attr, cf in classified.values():
            pe = cf.planner_evaluation
            assert pe.observed_trajectory and pe.corrected_trajectory
            assert pe.engine
            for sa in (cf.observed_safety, cf.corrected_safety):
                assert sa.max_braking_mps2 >= 0.0
                assert "surrogate" in sa.surrogate_caveat.lower() or \
                    "TTC" in sa.surrogate_caveat

    def test_counterfactual_provenance_labeled(self, classified):
        for sc, v, attr, cf in classified.values():
            assert cf.provenance.source == "COUNTERFACTUAL"
            assert cf.corrected_layers