File size: 3,914 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
86
87
88
89
90
91
92
/** Leadership-facing retrospective scorecard with per-field provenance tags
 * and JSON export. */
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Chip from '@mui/material/Chip';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import type { Scorecard, ScorecardField } from '../../types/agentic';
import { OutcomeChip, PanelTitle } from './common';

const TAG_COLORS: Record<ScorecardField['tag'], { bg: string; fg: string }> = {
  OBSERVED: { bg: '#1b5e20', fg: '#a5d6a7' },
  PREDICTED: { bg: '#0d47a1', fg: '#90caf9' },
  HYPOTHETICAL: { bg: '#4a148c', fg: '#ce93d8' },
  REQUIRED_EVIDENCE: { bg: '#4e342e', fg: '#ffcc80' },
};

const FIELDS: { key: keyof Scorecard; label: string }[] = [
  { key: 'failure_summary', label: 'Failure' },
  { key: 'frequency', label: 'Frequency' },
  { key: 'exposure', label: 'Exposure' },
  { key: 'severity', label: 'Severity' },
  { key: 'confidence', label: 'Confidence' },
  { key: 'novelty', label: 'Novelty' },
  { key: 'concentration', label: 'Concentration' },
  { key: 'downstream_impact', label: 'Downstream impact' },
  { key: 'mitigations', label: 'Mitigations' },
  { key: 'residual_risk', label: 'Residual risk' },
];

function renderValue(v: unknown): string {
  if (v === null || v === undefined) return '—';
  if (typeof v === 'object') return JSON.stringify(v);
  return String(v);
}

export default function ScorecardView({ card }: { card: Scorecard }) {
  const exportJson = () => {
    const blob = new Blob([JSON.stringify(card, null, 2)], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `${card.scorecard_id}.json`;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <Paper variant="outlined" sx={{ p: 1.5, bgcolor: '#161b22' }}>
      <PanelTitle
        title={`Agentic Safety Scorecard — ${card.title}`}
        origin="deterministic"
        extra={
          <Box sx={{ display: 'flex', gap: 0.75, alignItems: 'center' }}>
            <Chip size="small" label={`evidence: ${card.evidence_quality}`} sx={{ height: 20, fontSize: 10.5, fontWeight: 700, bgcolor: '#232a31' }} />
            <OutcomeChip outcome={card.policy_outcome} />
            <Button size="small" variant="outlined" onClick={exportJson}>
              Export JSON
            </Button>
          </Box>
        }
      />
      <Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' }, gap: 1 }}>
        {FIELDS.map(({ key, label }) => {
          const f = card[key] as ScorecardField;
          const tag = TAG_COLORS[f.tag];
          return (
            <Paper key={key} variant="outlined" sx={{ p: 1, bgcolor: '#12171d' }}>
              <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, mb: 0.5 }}>
                <Typography variant="caption" sx={{ fontWeight: 800, flex: 1 }}>
                  {label}
                </Typography>
                <Chip size="small" label={f.tag.replace('_', ' ')} sx={{ height: 16, fontSize: 9, fontWeight: 800, bgcolor: tag.bg, color: tag.fg }} />
              </Box>
              <Typography variant="caption" component="div" sx={{ color: '#c3ccd4', wordBreak: 'break-word' }}>
                {renderValue(f.value)}
              </Typography>
              <Typography variant="caption" component="div" sx={{ color: '#5c6770', fontSize: 10, mt: 0.5 }}>
                evidence: {f.evidence_ref || '—'}
              </Typography>
            </Paper>
          );
        })}
      </Box>
      <Typography variant="caption" component="div" sx={{ color: '#5c6770', mt: 1 }}>
        {card.notes.join(' · ')} · recommended option: {card.recommended_option ?? '—'} · policy{' '}
        {card.policy_version} · generated {card.generated_at}
      </Typography>
    </Paper>
  );
}