File size: 3,582 Bytes
5d10875
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
/** Perception → tracking → prediction → planner → behavior chain with
 * explicit UNCERTAIN / UNAVAILABLE labeling. */
import Box from '@mui/material/Box';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import type { AgentResult } from '../../types/agentic';
import { EVIDENCE_STATUS_COLORS, KV, PanelTitle } from './common';

interface ChainLink {
  link: string;
  status: 'OBSERVED' | 'DERIVED' | 'HYPOTHESIS' | 'UNAVAILABLE';
  detail: string;
}

export default function SafetyChainPanel({ safety }: { safety: AgentResult }) {
  const chain = (safety.output.chain as ChainLink[] | undefined) ?? [];
  const kinematics = safety.output.kinematics as Record<string, unknown> | undefined;
  const worst = safety.output.worst_case as Record<string, unknown> | undefined;
  const uncertain = safety.output.behavioral_evidence === 'none';
  return (
    <Paper variant="outlined" sx={{ p: 1.5, bgcolor: '#161b22' }}>
      <PanelTitle title="Safety Impact Chain" origin="ai" />
      {uncertain ? (
        <Typography
          variant="body2"
          sx={{ color: '#f9a825', fontWeight: 700, mb: 1 }}
        >
          {String(safety.output.assessment)}
        </Typography>
      ) : null}
      <Box sx={{ display: 'flex', flexWrap: 'wrap', alignItems: 'stretch', gap: 0.5, mb: 1 }}>
        {chain.map((c, i) => (
          <Box key={c.link} sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
            <Paper
              variant="outlined"
              sx={{
                p: 0.75,
                minWidth: 130,
                bgcolor: '#12171d',
                borderTop: `3px solid ${EVIDENCE_STATUS_COLORS[c.status]}`,
              }}
            >
              <Typography variant="caption" sx={{ fontWeight: 800, display: 'block' }}>
                {c.link.replace(/_/g, ' ')}
              </Typography>
              <Typography variant="caption" sx={{ color: EVIDENCE_STATUS_COLORS[c.status], fontWeight: 700 }}>
                {c.status === 'UNAVAILABLE' ? 'UNCERTAIN — no evidence' : c.status}
              </Typography>
              <Typography variant="caption" component="div" sx={{ color: '#8a949e', fontSize: 10.5 }}>
                {c.detail}
              </Typography>
            </Paper>
            {i < chain.length - 1 ? <Typography sx={{ color: '#5c6770' }}>→</Typography> : null}
          </Box>
        ))}
      </Box>
      {!uncertain && worst ? (
        <Box>
          <KV k="Worst replay min TTC" v={`${String(worst.min_ttc_s)} s`} mono />
          <KV k="Worst replay max DRAC" v={`${String(worst.max_drac)} m/s²`} mono />
          <KV k="Max collision probability" v={String(worst.max_p)} mono />
          {kinematics ? (
            <>
              <KV
                k="Stopping distance (nominal → delayed)"
                v={`${String(kinematics.stopping_distance_nominal_m)} m → ${String(kinematics.stopping_distance_delayed_m)} m`}
                mono
              />
              <KV
                k="Braking margin (nominal → delayed)"
                v={`${String(kinematics.braking_margin_nominal_m)} m → ${String(kinematics.braking_margin_delayed_m)} m`}
                mono
              />
              <KV k="Kinematics method" v={String(kinematics.method)} />
            </>
          ) : null}
          <Typography variant="caption" component="div" sx={{ color: '#5c6770', mt: 0.5 }}>
            {String(safety.output.coverage_note ?? '')}
          </Typography>
        </Box>
      ) : null}
    </Paper>
  );
}