/**
* 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 }
/>
: }
disabled={busy}
onClick={() => void runSearch()}
>
Search errors
{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.
)}
);
}