/** Visualization-heavy stage views: distribution shift, conditional * performance heatmap (Simpson's paradox surface), paired transition matrix, * significance CI plot, feature parity ranked deltas. */ import Box from '@mui/material/Box'; import Chip from '@mui/material/Chip'; import Tooltip from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; import { BORDER, DeltaBar, Explainer, KV, PsiBadge, SectionCard, StatusPill, tableSx, } from './common'; // ------------------------------------------------ stage 3: distribution shift export function ShiftView({ data }: { data: any }) { if (data.volume_ok === false) { return ( <> Too little data to assess shift: PSI estimates are upward-biased at this volume, so shift can be neither confirmed nor excluded. This is itself a finding — record it and proceed with the unknown acknowledged. ); } const renderRows = (rows: any[]) => ( Dimension PSI (practical effect) JS div Test p-value (see caveat) Means (off → sh) {rows.map((r: any) => ( {r.dimension} {r.js.toFixed(3)} {r.test}={r.stat.toFixed(2)} {r.p_value < 0.001 ? '<0.001' : r.p_value.toFixed(3)} {r.offline_mean !== undefined ? `${r.offline_mean.toFixed(1)} → ${r.shadow_mean.toFixed(1)}` : '—'} ))} ); return ( <> {renderRows(data.segments)} {renderRows(data.features)} ⚠ {data.caveat} ); } // ------------------------------------------ stage 4: conditional performance function heatColor(v: number, cap = 12): string { const t = Math.max(-1, Math.min(1, v / cap)); if (t >= 0) { const a = Math.round(40 + t * 150); return `rgba(102, 187, 106, ${a / 255})`; } const a = Math.round(40 + -t * 170); return `rgba(239, 83, 80, ${a / 255})`; } export function ConditionalHeatmapView({ data }: { data: any }) { const rows: any[] = data.rows; const scenes = Array.from(new Set(rows.map((r) => r.scene))).sort(); const times = Array.from(new Set(rows.map((r) => r.time_of_day))).sort(); const cell = (scene: string, tod: string) => rows.find((r) => r.scene === scene && r.time_of_day === tod); const grid = (which: 'offline_delta_pp' | 'shadow_delta_pp', title: string) => ( {title} {times.map((t) => ( {t} ))} {scenes.map((s) => ( {s} {times.map((t) => { const c = cell(s, t); const v = c ? c[which] : 0; return ( {v >= 0 ? '+' : ''}{v.toFixed(1)} ); })} ))} ); const agg = data.aggregate; return ( <> {grid('offline_delta_pp', `OFFLINE (aggregate ${agg.offline_delta_pp >= 0 ? '+' : ''}${agg.offline_delta_pp.toFixed(1)}pp)`)} {grid('shadow_delta_pp', `SHADOW (aggregate ${agg.shadow_delta_pp >= 0 ? '+' : ''}${agg.shadow_delta_pp.toFixed(1)}pp)`)} Within-segment sign consistency: {(agg.sign_consistency * 100).toFixed(0)}% of scored volume. SegmentOffline ΔShadow ΔVol (off/sh)Shift ×Interpretation {rows.map((r) => ( {r.segment} {r.offline_n}/{r.shadow_n} {r.shift_ratio.toFixed(2)} ))} ); } // ------------------------------------------------ stage 5: paired comparison function TransitionMatrix({ m, title }: { m: any; title: string }) { const cellSx = (bg: string, color: string) => ({ width: 130, height: 62, borderRadius: 1, m: 0.25, p: 1, bgcolor: bg, border: `1px solid ${BORDER}`, display: 'flex', flexDirection: 'column', justifyContent: 'center', color, }); return ( {title} (n={m.n.toLocaleString()}) B correct B incorrect A correct both correct {m.both_correct.toLocaleString()} REGRESSIONS ↓ {m.regressions.toLocaleString()} A incorrect improvements ↑ {m.improvements.toLocaleString()} both wrong {m.both_wrong.toLocaleString()} net {m.net_pp >= 0 ? '+' : ''}{m.net_pp.toFixed(1)}pp ); } export function PairedTransitionView({ data }: { data: any }) { const drill = (rows: any[], label: string) => ( {label}Regressions (A✓→B✗)Improvements (A✗→B✓) {rows[0]?.lift !== undefined ? Unit share : null} {rows[0]?.lift !== undefined ? Regression lift : null} Net {rows.map((r) => ( {r.segment} {r.regressions} {r.improvements} {r.lift !== undefined ? {(r.unit_share * 100).toFixed(0)}% : null} {r.lift !== undefined ? ( = 1.5 ? '#ef535022' : '#232a31', color: r.lift >= 1.5 ? '#ef5350' : '#aab4be', }} /> ) : null} ))} ); return ( <> {drill(data.by_band, `serving-confidence band (${data.conf_col})`)} {drill(data.by_segment, 'segment')} {drill(data.by_class, 'object class')} ); } // ---------------------------------------------------- stage 6: significance function CiPlot({ res, marginPp }: { res: any; marginPp: number }) { // Scale: pp values mapped onto an SVG x-axis. const w = 560; const h = 90; const lo = Math.min(res.ci_low * 100, -marginPp) - 1.2; const hi = Math.max(res.ci_high * 100, marginPp) + 1.2; const x = (v: number) => ((v - lo) / (hi - lo)) * (w - 40) + 20; const y = 38; return ( {/* practical-regression zone */} −{marginPp.toFixed(1)}pp practical margin 0 {/* CI */} {res.delta * 100 >= 0 ? '+' : ''}{(res.delta * 100).toFixed(2)}pp {(res.ci_low * 100).toFixed(2)} {(res.ci_high * 100).toFixed(2)} ); } const OUTCOME_LABEL: Record = { significant_regression: { label: 'SIGNIFICANT REGRESSION', color: '#ef5350' }, no_significant_difference: { label: 'NO SIGNIFICANT DIFFERENCE', color: '#66bb6a' }, insufficient_evidence: { label: 'INSUFFICIENT EVIDENCE', color: '#ffb74d' }, }; export function SignificanceView({ data }: { data: any }) { const o = OUTCOME_LABEL[data.outcome]; const sp = data.shadow_paired; return ( <> } > Engine: {data.engine} — {data.engine_note} {data.seqeval ? ( } /> {data.seqeval.note} ) : null} MeasurementRate95% CIn {Object.entries(data.wilson as Record).map(([k, v]) => ( {k.replace('_', ' · model ')} {(v.rate * 100).toFixed(2)}% [{(v.ci_low * 100).toFixed(2)}, {(v.ci_high * 100).toFixed(2)}] {v.n.toLocaleString()} ))} ); } // --------------------------------------------------- stage 7: feature parity export function FeatureParityView({ data }: { data: any }) { const maxSmd = Math.max(...data.rows.map((r: any) => r.within_segment_smd), 1); return ( <> FeatureWithin-segment SMDRaw SMD Mean (off → sh)Median ratioMissing (off / sh)Verdict {data.rows.map((r: any) => ( {r.feature} {r.within_segment_smd.toFixed(2)} {r.smd.toFixed(2)} {r.offline_mean.toFixed(1)} → {r.shadow_mean.toFixed(1)} ×{r.median_ratio.toFixed(2)} {(r.offline_missing * 100).toFixed(1)}% / {(r.shadow_missing * 100).toFixed(1)}% ))} ); }