/** * Investigation panel: multi-criteria error search against the inverted error * index. Results stay aggregate-first (counts, by-type shares, worst * containers) — clicking a container jumps to the forensic drill-down. */ import { useState } from 'react'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Checkbox from '@mui/material/Checkbox'; import Chip from '@mui/material/Chip'; import CircularProgress from '@mui/material/CircularProgress'; import FormControlLabel from '@mui/material/FormControlLabel'; import MenuItem from '@mui/material/MenuItem'; import Table from '@mui/material/Table'; import TableBody from '@mui/material/TableBody'; import TableCell from '@mui/material/TableCell'; import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import { SearchCode, ShieldAlert } from 'lucide-react'; import type { DimName, ErrorSearchResponse, ErrorType } from '../../types/megaeval'; import { fmtCompact, getDimensions, searchErrors } from '../../services/megaeval'; import { usePoll } from '../../services/labeleval'; import { ErrorNote, MetricCard, SectionCard, fmtNum } from '../labeleval/shared'; import { HeadCell } from '../help/InfoTip'; import { DimChips, ERROR_TYPE_COLORS, OutcomeChip } from './shared'; const ALL_ERROR_TYPES: ErrorType[] = ['FN', 'FP', 'LOCALIZATION', 'ANOMALY', 'LOW_CONF']; function MultiSelect({ label, options, value, onChange, minWidth = 170, }: { label: string; options: string[]; value: string[]; onChange: (v: string[]) => void; minWidth?: number; }) { return ( { const v = e.target.value as unknown; onChange(typeof v === 'string' ? v.split(',').filter(Boolean) : (v as string[])); }} SelectProps={{ multiple: true }} sx={{ minWidth }} > {options.map((o) => ( {o} ))} ); } export default function InvestigationTab({ runId, onOpenContainer, }: { runId: string; onOpenContainer: (cid: number) => void; }) { const dims = usePoll(getDimensions, null, []); const dimOptions = (d: DimName): string[] => dims.data?.dimensions[d] ?? []; const [errorTypes, setErrorTypes] = useState([]); const [classes, setClasses] = useState([]); const [lightings, setLightings] = useState([]); const [weathers, setWeathers] = useState([]); const [confMax, setConfMax] = useState(''); const [riskMin, setRiskMin] = useState(''); const [safetyOnly, setSafetyOnly] = useState(false); const [result, setResult] = useState(null); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const runSearch = async () => { setBusy(true); setError(null); try { const filters: Partial> = {}; if (classes.length) filters.class = classes; if (lightings.length) filters.lighting = lightings; if (weathers.length) filters.weather = weathers; const confParsed = Number(confMax); const riskParsed = Number(riskMin); setResult( await searchErrors({ run_id: runId, ...(errorTypes.length ? { error_types: errorTypes as ErrorType[] } : {}), ...(Object.keys(filters).length ? { filters } : {}), ...(confMax.trim() !== '' && Number.isFinite(confParsed) ? { confidence_max: confParsed } : {}), ...(riskMin.trim() !== '' && Number.isFinite(riskParsed) ? { risk_min: riskParsed } : {}), ...(safetyOnly ? { safety_only: true } : {}), limit_containers: 20, }) ); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setBusy(false); } }; return ( setConfMax(e.target.value)} sx={{ width: 130 }} inputProps={{ step: 0.05, min: 0, max: 1 }} /> setRiskMin(e.target.value)} sx={{ width: 110 }} inputProps={{ step: 0.1, min: 0 }} /> setSafetyOnly(e.target.checked)} />} label={Safety-critical only} /> {error ? : null} {result ? ( <> {ALL_ERROR_TYPES.filter((t) => (result.by_type[t] ?? 0) > 0).map((t) => ( ))} Context {result.worst_containers.map((c) => ( onOpenContainer(c.container_id)} sx={{ cursor: 'pointer' }} > #{c.container_id} {c.error_count} {fmtNum(c.mean_risk, 2)} {fmtNum(c.max_severity, 2)} 0 ? '#ef5350' : undefined }}> {c.safety_hits} ))}
Click a container to open its forensic object table.
{result.examples.map((ex) => ( onOpenContainer(ex.container_id)} > {ex.safety_critical ? : null} {ex.class} sev {fmtNum(ex.severity, 2)} · conf {fmtNum(ex.confidence, 2)} · risk {fmtNum(ex.risk_score, 2)} · # {ex.container_id} ))}
) : ( Define criteria and search the error index — results are served from the inverted index, not a scan. )}
); }