File size: 14,032 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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
"""Hardware/domain dimensions as first-class gate strata.

A release that passes globally can still be unshippable on one platform in
one city with one sensor generation. This module builds a gate matrix whose
rows are combinations of:

    compute_platform   real device configs from sensorflow.vitis.backend
                       (KNOWN_DEVICES) + the reference CPU harness
    sensor_config      the megaeval 'sensor' cube dimension (lidar/camera/fused)
    sensor_generation  config-mapped generation label per sensor_config
    calibration_version latest sensorflow.safety.calibration status
    firmware           config-mapped per compute_platform
    region             domain stratum proxied from the megaeval 'road_type'
                       cube dimension (the derivation is recorded per row)

Metrics per combination come from REAL per-cohort data (megaeval metric cube
of the latest published run). Platform-specific evidence exists only where a
vitis HIL run exists for that device; combinations without evidence are
reported INSUFFICIENT — never assumed passing.

evaluate_matrix() is a pure function of (rows, policy) so the gate logic is
testable on planted inputs, separate from data synthesis.

Minimum-evidence math is delegated to hardening/seqeval power utilities when
importable; the fallback is a Wilson-interval width bound.
"""

from __future__ import annotations

import math
import os
from typing import Dict, List, Optional

from sensorflow.studio2 import store
from sensorflow.studio2.registry import content_hash

# ------------------------------------------------------------------ policy

DEFAULT_HARDWARE_POLICY: Dict = {
    "policy_name": "studio2-hardware-v1",
    "metrics": {
        "recall": {"min": 0.80},
        "precision": {"min": 0.90},
    },
    # evidence adequacy: a combination needs enough support that a Wilson CI
    # on its pass proportion is narrower than this (min-n delegated to
    # hardening/seqeval when importable)
    "max_ci_width": 0.10,
    # combinations matching any of these dicts are launch-critical: a failure
    # there blocks even when the global aggregate passes
    "critical_combinations": [
        {"region": "san_francisco", "sensor_config": "lidar"},
        {"sensor_config": "fused"},
    ],
}

# region strata proxied from the megaeval road_type dimension; the mapping is
# reported on every row so nobody mistakes it for geo-tagged data
REGION_PROFILES = {
    "san_francisco": {"road_type": "urban"},
    "phoenix": {"road_type": "highway"},
    "rural_pilot": {"road_type": "rural"},
}

SENSOR_GENERATIONS = {"lidar": "LiDAR-Gen2", "camera": "Camera-Gen3",
                      "fused": "Fusion-Stack-v1"}

FIRMWARE = {"cpu-reference": "host-fp32", "versal-ai-edge": "vitis-2024.2",
            "zynq-ultrascale": "vitis-2024.2"}


# ------------------------------------------------------------------ min-n


def min_support(max_ci_width: float, p: float = 0.85) -> Dict:
    """Minimum per-combination sample size for adequate evidence.

    Delegates to hardening.power when that package is importable (n to detect
    a proportion drop of max_ci_width/2 below the threshold, one-sided);
    otherwise a Wilson/normal-approximation bound: the CI full width at
    proportion p is ~ 2*z*sqrt(p(1-p)/n).
    """
    try:
        from sensorflow.hardening import power as hardening_power  # type: ignore
        n = int(hardening_power.required_n_two_proportions(
            baseline_rate=p, mde=max_ci_width / 2.0, two_sided=False))
        return {"n": n, "method": "hardening.power.required_n_two_proportions "
                f"(one-sided, p={p}, mde={max_ci_width / 2.0})"}
    except Exception:
        pass
    z = 1.959964  # 95%
    n = math.ceil((2.0 * z / max_ci_width) ** 2 * p * (1.0 - p))
    return {"n": int(n), "method": "wilson/normal-approx fallback "
            f"(z=1.96, p={p}, full width<={max_ci_width})"}


# ------------------------------------------------------------------ synthesis


def _latest_published_mega_run():
    """Returns (mega_store, run_object) for the newest published run."""
    from sensorflow.megaeval.runs import get_mega_store
    store_ = get_mega_store()
    published = [r for r in store_.runs.values()
                 if getattr(r, "status", None) == "published"]
    if not published:
        raise RuntimeError("no published megaeval runs")
    published.sort(key=lambda r: getattr(r, "published_at", None)
                   or getattr(r, "created_at", "") or "")
    return store_, published[-1]


def _cohort_metrics(mega_store, run_id: str, sensor: str, road_type: str) -> Optional[Dict]:
    from sensorflow.megaeval import cube as cube_mod
    cube_df = mega_store.artifacts(run_id)["cube"]
    rows, _ = cube_mod.aggregate(cube_df,
                                 {"sensor": [sensor], "road_type": [road_type]},
                                 None, ["n", "recall", "precision"], 1)
    return rows[0] if rows else None


def _vitis_platforms() -> Dict[str, Dict]:
    """Real device configs + whether any HIL evidence exists for them."""
    platforms: Dict[str, Dict] = {
        "cpu-reference": {"description": "reference CPU harness (fp32)",
                          "hil_evidence": True,
                          "evidence_note": "metrics measured directly on this harness"}}
    try:
        from sensorflow.vitis.backend import KNOWN_DEVICES
        hil_dir = os.path.join("runs", "vitis", "hil")
        has_hil = os.path.isdir(hil_dir) and any(
            n.startswith(("hilrun-", "hilsweep-")) for n in os.listdir(hil_dir))
        for name, spec in KNOWN_DEVICES.items():
            platforms[name] = {
                "description": spec.get("description", name),
                "hil_evidence": bool(has_hil),
                "evidence_note": ("vitis HIL quantization-gap runs present"
                                  if has_hil else
                                  "no vitis HIL run for this device"),
            }
    except Exception:
        pass
    return platforms


def synthesize_combinations() -> Dict:
    """Build combination rows from the real stores. Raises RuntimeError when
    the megaeval store has no published run (callers surface that as an
    unavailable matrix, they do not invent one)."""
    mega_store, run = _latest_published_mega_run()
    run_id = run.run_id
    calibration = None
    try:
        from sensorflow.safety import calibration as calib_mod
        cal = calib_mod.latest_status()
        calibration = (cal or {}).get("status")
    except Exception:
        pass
    calibration_version = f"calib-{(calibration or 'UNKNOWN').lower()}"

    platforms = _vitis_platforms()
    sensors = ["lidar", "camera", "fused"]

    rows: List[Dict] = []
    for platform, pinfo in platforms.items():
        for sensor in sensors:
            for region, proxy in REGION_PROFILES.items():
                combo = {
                    "compute_platform": platform,
                    "sensor_config": sensor,
                    "sensor_generation": SENSOR_GENERATIONS.get(sensor, "unknown"),
                    "calibration_version": calibration_version,
                    "firmware": FIRMWARE.get(platform, "unknown"),
                    "region": region,
                }
                row: Dict = {
                    "combination": combo,
                    "combination_label": f"{region} × {platform} × "
                                         f"{SENSOR_GENERATIONS.get(sensor, sensor)}",
                    "derivation": {
                        "metrics_source": f"megaeval cube of run {run_id}, "
                                          f"filter sensor={sensor}, "
                                          f"road_type={proxy['road_type']} "
                                          f"(region proxied from road_type)",
                        "platform_evidence": pinfo["evidence_note"],
                    },
                }
                if not pinfo["hil_evidence"]:
                    row.update({"metrics": None, "n": 0,
                                "evidence": "MISSING",
                                "evidence_reason": pinfo["evidence_note"]})
                else:
                    m = _cohort_metrics(mega_store, run_id, sensor,
                                        proxy["road_type"])
                    if m is None:
                        row.update({"metrics": None, "n": 0,
                                    "evidence": "MISSING",
                                    "evidence_reason": "no cube cells for this cohort"})
                    else:
                        row.update({"metrics": {"recall": m.get("recall"),
                                                "precision": m.get("precision")},
                                    "n": int(m.get("n") or 0),
                                    "evidence": "PRESENT"})
                rows.append(row)

    # global aggregate for the "global pass but critical combo fails" check
    headline = getattr(run, "headline", None) or {}
    global_metrics = {"recall": headline.get("recall"),
                      "precision": headline.get("precision")}
    return {"rows": rows, "global_metrics": global_metrics,
            "source_run_id": run_id}


# ------------------------------------------------------------------ evaluation


def _check_metrics(metrics: Optional[Dict], thresholds: Dict) -> List[str]:
    failed = []
    for metric, spec in thresholds.items():
        val = (metrics or {}).get(metric)
        if val is None:
            failed.append(f"{metric} missing")
        elif "min" in spec and val < spec["min"]:
            failed.append(f"{metric} {val:.3f} < {spec['min']}")
        elif "max" in spec and val > spec["max"]:
            failed.append(f"{metric} {val:.3f} > {spec['max']}")
    return failed


def _matches(combo: Dict, pattern: Dict) -> bool:
    return all(combo.get(k) == v for k, v in pattern.items())


def evaluate_matrix(rows: List[Dict], policy: Optional[Dict] = None,
                    global_metrics: Optional[Dict] = None,
                    source_run_id: Optional[str] = None,
                    persist: bool = True) -> Dict:
    """Pure gate logic over combination rows.

    Each row: {combination: {...}, combination_label, metrics|None, n,
    evidence: PRESENT|MISSING, [evidence_reason]}.
    """
    pol = {**DEFAULT_HARDWARE_POLICY, **(policy or {})}
    support = min_support(pol["max_ci_width"])
    thresholds = pol["metrics"]

    evaluated: List[Dict] = []
    insufficient: List[Dict] = []
    failures: List[Dict] = []
    critical_failures: List[Dict] = []

    for row in rows:
        out = dict(row)
        critical = any(_matches(row["combination"], pat)
                       for pat in pol["critical_combinations"])
        out["critical"] = critical
        if row.get("evidence") != "PRESENT" or row.get("metrics") is None:
            out["status"] = "INSUFFICIENT"
            out["reason"] = row.get("evidence_reason") or "no evidence"
            insufficient.append(out)
        elif int(row.get("n") or 0) < support["n"]:
            out["status"] = "INSUFFICIENT"
            out["reason"] = (f"support n={row.get('n')} below minimum "
                             f"{support['n']} ({support['method']})")
            insufficient.append(out)
        else:
            failed = _check_metrics(row["metrics"], thresholds)
            if failed:
                out["status"] = "FAIL"
                out["failed_checks"] = failed
                failures.append(out)
                if critical:
                    critical_failures.append(out)
            else:
                out["status"] = "PASS"
        evaluated.append(out)

    global_failed = _check_metrics(global_metrics, thresholds) if global_metrics else None
    global_pass = (global_failed == []) if global_failed is not None else None

    if critical_failures:
        status = "FAIL_CRITICAL"
    elif failures:
        status = "FAIL"
    elif not any(r["status"] == "PASS" for r in evaluated):
        status = "INSUFFICIENT"
    else:
        status = "PASS"

    report = {
        "matrix_id": f"hwm-{content_hash({'rows': [r.get('combination') for r in rows], 'policy': pol}, exclude=())}",
        "status": status,
        "global_pass": global_pass,
        "global_metrics": global_metrics,
        "global_vs_matrix_note": (
            "global aggregate passes but a launch-critical combination fails — "
            "the aggregate is hiding a stratum failure (Simpson-style); the "
            "matrix verdict wins"
            if global_pass and critical_failures else None),
        "n_combinations": len(evaluated),
        "n_pass": sum(1 for r in evaluated if r["status"] == "PASS"),
        "n_fail": len(failures),
        "n_insufficient": len(insufficient),
        "min_support": support,
        "critical_failures": critical_failures,
        "failures": [f for f in failures if f not in critical_failures],
        "insufficient": [{"combination_label": r["combination_label"],
                          "combination": r["combination"],
                          "critical": r["critical"],
                          "reason": r["reason"]} for r in insufficient],
        "rows": evaluated,
        "policy": pol,
        "source_run_id": source_run_id,
        "evaluated_at": store.now_iso(),
    }
    if persist:
        store.write_json(report, "hardware", "latest.json")
    return report


def gate_matrix(policy: Optional[Dict] = None, persist: bool = True) -> Dict:
    """Synthesize combinations from the real stores and evaluate them."""
    data = synthesize_combinations()
    return evaluate_matrix(data["rows"], policy=policy,
                           global_metrics=data["global_metrics"],
                           source_run_id=data["source_run_id"],
                           persist=persist)


def latest_matrix() -> Optional[Dict]:
    return store.read_json("hardware", "latest.json")