"""Cross-run analysis: model-vs-model comparison, distribution shift, the "why" layer, and hybrid similarity search. All computations run on cubes and the error index — never on raw records. """ from __future__ import annotations import json import os from typing import Dict, List, Optional import numpy as np import pandas as pd from sensorflow.megaeval import cube as cube_mod from sensorflow.megaeval import population as pop_mod from sensorflow.megaeval.population import DIMENSIONS, DIM_NAMES from sensorflow.megaeval.runs import ERROR_TYPES, EvaluationRun, MegaStore, run_dir # ------------------------------------------------------------------ model vs model DEFAULT_PROMOTION_POLICY = { "max_recall_drop": 0.010, "max_precision_drop": 0.015, "max_safety_recall_drop": 0.005, "max_cohort_recall_drop": 0.050, "min_cohort_support": 200, "cohort_dims": ["road_type", "lighting", "class"], } _HEADLINE_METRICS = ["precision", "recall", "f1", "mean_iou", "safety_recall", "anomaly_rate"] def compare_runs(store: MegaStore, candidate: EvaluationRun, baseline: EvaluationRun, policy: Optional[Dict] = None) -> Dict: policy = {**DEFAULT_PROMOTION_POLICY, **(policy or {})} cand_art, base_art = store.artifacts(candidate.run_id), store.artifacts(baseline.run_id) cand_cube, base_cube = cand_art["cube"], base_art["cube"] headline = [] for m in _HEADLINE_METRICS: c, b = candidate.headline.get(m), baseline.headline.get(m) if c is None or b is None: continue headline.append({"metric": m, "baseline": round(b, 4), "candidate": round(c, 4), "delta": round(c - b, 4)}) per_class = [] for cls in DIMENSIONS["class"]: c, b = candidate.per_class.get(cls), baseline.per_class.get(cls) if not c or not b: continue per_class.append({ "class": cls, "n": c.get("n"), "recall_baseline": b.get("recall"), "recall_candidate": c.get("recall"), "recall_delta": round((c.get("recall") or 0) - (b.get("recall") or 0), 4), "precision_baseline": b.get("precision"), "precision_candidate": c.get("precision"), "precision_delta": round((c.get("precision") or 0) - (b.get("precision") or 0), 4), }) dims = policy["cohort_dims"] cand_rows, _ = cube_mod.aggregate(cand_cube, None, dims, ["n", "recall", "precision"], 2000) base_rows, _ = cube_mod.aggregate(base_cube, None, dims, ["n", "recall", "precision"], 2000) base_map = {tuple(r[d] for d in dims): r for r in base_rows} cohorts = [] for r in cand_rows: key = tuple(r[d] for d in dims) b = base_map.get(key) if not b or r["n"] < policy["min_cohort_support"]: continue if r.get("recall") is None or b.get("recall") is None: continue cohorts.append({ "cohort": "/".join(key), **{d: r[d] for d in dims}, "n": r["n"], "recall_baseline": b["recall"], "recall_candidate": r["recall"], "recall_delta": round(r["recall"] - b["recall"], 4), }) cohorts.sort(key=lambda x: x["recall_delta"]) regressions = [c for c in cohorts if c["recall_delta"] < -policy["max_cohort_recall_drop"]] blockers: List[str] = [] hd = {h["metric"]: h["delta"] for h in headline} if hd.get("recall", 0) < -policy["max_recall_drop"]: blockers.append(f"headline recall dropped {hd['recall']:+.4f} " f"(policy allows -{policy['max_recall_drop']})") if hd.get("precision", 0) < -policy["max_precision_drop"]: blockers.append(f"headline precision dropped {hd['precision']:+.4f}") if hd.get("safety_recall", 0) < -policy["max_safety_recall_drop"]: blockers.append(f"safety recall dropped {hd['safety_recall']:+.4f} " f"(policy allows -{policy['max_safety_recall_drop']})") for c in regressions[:5]: blockers.append( f"REGRESSION: {c['cohort']} recall " f"{c['recall_baseline']*100:.1f}% -> {c['recall_candidate']*100:.1f}% " f"(Δ {c['recall_delta']*100:+.1f})") # integrate with the existing regression tracker so the Regression page shows it try: from sensorflow.evaluation import regression as legacy_regression from sensorflow.evaluation.records import get_store as get_legacy_store legacy_regression.compare_runs( get_legacy_store(), current_metrics={"precision": candidate.headline.get("precision"), "recall": candidate.headline.get("recall"), "safety_critical_recall": candidate.headline.get("safety_recall"), "mean_iou_3d": candidate.headline.get("mean_iou")}, model_version=candidate.model_version, dataset_version=candidate.population_id, run_id=candidate.run_id, baseline_metrics={"precision": baseline.headline.get("precision"), "recall": baseline.headline.get("recall"), "safety_critical_recall": baseline.headline.get("safety_recall"), "mean_iou_3d": baseline.headline.get("mean_iou")}, baseline_version=baseline.model_version, affected_classes=[c["class"] for c in per_class if c["recall_delta"] < -0.03], affected_scenarios=sorted({c.get("road_type", "") for c in regressions[:5] if c}), ) except Exception: pass return { "candidate": {"run_id": candidate.run_id, "model_version": candidate.model_version}, "baseline": {"run_id": baseline.run_id, "model_version": baseline.model_version}, "headline_deltas": headline, "per_class": per_class, "worst_cohorts": cohorts[:15], "regressions": regressions[:10], "policy": policy, "recommendation": "DO_NOT_PROMOTE" if blockers else "PROMOTE", "blockers": blockers, } # ------------------------------------------------------------------ distribution shift def distribution_shift(store: MegaStore, run: EvaluationRun, min_eval_count: int = 300, rel_threshold: float = 0.35) -> Dict: cache_path = os.path.join(run_dir(run.run_id), "shift.json") if os.path.exists(cache_path): with open(cache_path) as f: return json.load(f) meta = pop_mod.load_meta(run.population_id) cube_df = store.artifacts(run.run_id)["cube"] overall_rows, _ = cube_mod.aggregate(cube_df, None, None, ["recall"], 1) overall_recall = overall_rows[0].get("recall") shifts = [] for row in meta.get("train_mix", []): train, evals = row["train_share"], row["eval_share"] if train <= 0 or row["eval_count"] < min_eval_count: continue rel = (evals - train) / train if abs(rel) < rel_threshold: continue filt = {"class": [row["class"]], "weather": [row["weather"]], "lighting": [row["lighting"]]} crows, _ = cube_mod.aggregate(cube_df, filt, None, ["n", "recall", "precision"], 1) c = crows[0] if crows else {} shifts.append({ "cohort": f"{row['class']}/{row['weather']}/{row['lighting']}", "class": row["class"], "weather": row["weather"], "lighting": row["lighting"], "train_share": round(train, 5), "eval_share": round(evals, 5), "relative_change": round(rel, 4), "eval_count": row["eval_count"], "cohort_recall": c.get("recall"), "overall_recall": overall_recall, "recall_gap": (round(c["recall"] - overall_recall, 4) if c.get("recall") is not None and overall_recall is not None else None), }) shifts.sort(key=lambda s: abs(s["relative_change"]) * (1 + abs(s.get("recall_gap") or 0) * 5), reverse=True) # surface the worst shifts as alerts in the existing alerting system (once per run) try: from sensorflow.evaluation.records import Alert, get_store as get_legacy_store, new_id legacy = get_legacy_store() for s in shifts[:3]: if s.get("recall_gap") is not None and s["recall_gap"] < -0.03: legacy.put("alerts", Alert( alert_id=new_id("alert"), kind="distribution_shift", severity="critical" if s["recall_gap"] < -0.08 else "warning", message=(f"Distribution shift {s['cohort']}: train {s['train_share']*100:.1f}% " f"-> eval {s['eval_share']*100:.1f}% " f"(rel {s['relative_change']*100:+.0f}%), cohort recall " f"{(s['cohort_recall'] or 0)*100:.1f}% vs overall " f"{(s['overall_recall'] or 0)*100:.1f}%"), evidence_page="command", evidence_id=run.run_id)) legacy.save() except Exception: pass result = {"run_id": run.run_id, "shifts": shifts[:20], "method": "train mix vs eval mix over (class, weather, lighting); " "relative share change with cube recall impact", "thresholds": {"min_eval_count": min_eval_count, "rel_threshold": rel_threshold}} with open(cache_path, "w") as f: json.dump(result, f) return result # ------------------------------------------------------------------ the "why" layer _FACTOR_ORDER = ["occlusion", "low_illumination", "long_range", "sensor_disagreement", "other"] def why(store: MegaStore, run: EvaluationRun, filters: Optional[Dict] = None, metric: str = "recall") -> Dict: """Decompose the failure population of a filtered slice by primary factor.""" art = store.artifacts(run.run_id) errors = art.get("errors") cube_df = art["cube"] if errors is None or not len(errors): return {"factors": [], "top_cohorts": [], "failure_count": 0} mask = np.ones(len(errors), dtype=bool) fail_types = [ERROR_TYPES.index(t) for t in ("FN", "FP", "LOCALIZATION")] if metric == "recall": fail_types = [ERROR_TYPES.index("FN"), ERROR_TYPES.index("LOCALIZATION")] elif metric == "precision": fail_types = [ERROR_TYPES.index("FP"), ERROR_TYPES.index("LOCALIZATION")] mask &= errors["error_type"].isin(fail_types).to_numpy() for dim, values in (filters or {}).items(): if dim not in DIM_NAMES: continue vocab = DIMENSIONS[dim] codes = [vocab.index(v) for v in (values if isinstance(values, list) else [values]) if v in vocab] mask &= errors[dim].isin(codes).to_numpy() sub = errors[mask] total = len(sub) if total == 0: return {"factors": [], "top_cohorts": [], "failure_count": 0} occl = sub["occlusion"].to_numpy() >= 1 night = sub["lighting"].to_numpy() == 1 far = sub["distance_band"].to_numpy() >= 2 sensor = sub["sensor_disagree"].to_numpy().astype(bool) factor = np.full(total, 4, dtype=np.int8) # other factor[sensor] = 3 factor[far] = 2 factor[night] = 1 factor[occl] = 0 # priority: occlusion > low light > range > sensor > other counts = np.bincount(factor, minlength=5) factors = [{"factor": _FACTOR_ORDER[i], "count": int(counts[i]), "share": round(float(counts[i]) / total, 4)} for i in range(5) if counts[i] > 0] factors.sort(key=lambda x: -x["count"]) rows, _ = cube_mod.aggregate(cube_df, filters, ["class", "lighting", "weather"], ["n", metric, "fn", "fp"], 2000) rows = [r for r in rows if r["n"] >= 150 and r.get(metric) is not None] rows.sort(key=lambda r: r[metric]) top = [{"cohort": f"{r['class']}/{r['lighting']}/{r['weather']}", "n": r["n"], metric: r[metric], "fn": r.get("fn"), "fp": r.get("fp")} for r in rows[:8]] return {"metric": metric, "filters": filters or {}, "failure_count": int(total), "factors": factors, "top_cohorts": top, "method": "primary-factor attribution over the error index " "(priority: occlusion > low illumination > long range > " "sensor disagreement > other)"} # ------------------------------------------------------------------ hybrid similarity def similar_containers(store: MegaStore, run: EvaluationRun, container_id: int, filters: Optional[Dict] = None, k: int = 12) -> Dict: art = store.artifacts(run.run_id) ids, emb = art["emb_ids"], art["emb"] containers = art["containers"].set_index("container_id") pos = np.where(ids == container_id)[0] if pos.size == 0: raise KeyError(f"Unknown container {container_id}") q = emb[pos[0]] sims = emb @ q mask = np.ones(ids.shape[0], dtype=bool) for dim, values in (filters or {}).items(): if dim not in pop_mod.CONTAINER_DIMS: continue vocab = DIMENSIONS[dim] codes = [vocab.index(v) for v in (values if isinstance(values, list) else [values]) if v in vocab] col = containers.loc[ids, dim].to_numpy() mask &= np.isin(col, codes) mask[pos[0]] = False sims = np.where(mask, sims, -np.inf) kk = min(k, int(mask.sum())) if kk <= 0: return {"query_container": container_id, "results": []} top = np.argpartition(-sims, kk - 1)[:kk] top = top[np.argsort(-sims[top])] results = [] for i in top.tolist(): cid = int(ids[i]) row = containers.loc[cid] results.append({ "container_id": cid, "similarity": round(float(sims[i]), 4), **{dim: DIMENSIONS[dim][int(row[dim])] for dim in pop_mod.CONTAINER_DIMS}, "n_objects": int(row["n_objects"]), "fn": int(row["fn"]), "fp": int(row["fp"]), "anomalies": int(row["anomalies"]), "risk_score": round(float(row["risk_score"]), 4), }) return {"query_container": container_id, "retrieval": "cosine over 32-d structural embeddings + structured filters", "results": results}