Spaces:
Paused
Paused
File size: 8,518 Bytes
d1ac27a | 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 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | /** Stage 12: recommended experiments, minimum-additional-evidence answer,
* rendered final report with remediation tiers, JSON/markdown export, and the
* training-mode reveal. */
import { useState } from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Chip from '@mui/material/Chip';
import Collapse from '@mui/material/Collapse';
import Typography from '@mui/material/Typography';
import { Download, Eye, FileJson } from 'lucide-react';
import type { Experiments, Investigation, RcaReport, RevealResponse } from '../../types/rca';
import { BORDER, CONFIDENCE_COLORS, Explainer, SectionCard, StatusPill, tableSx } from './common';
function download(filename: string, text: string, mime: string) {
const blob = new Blob([text], { type: mime });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
const COST_COLORS: Record<string, string> = { low: '#66bb6a', medium: '#ffb74d', high: '#ef5350' };
export function ExperimentsView({ exp }: { exp: Experiments }) {
return (
<>
<Explainer text={exp.explainer} />
<SectionCard title="Minimum additional evidence required">
<Typography variant="body2" sx={{ fontSize: 12.5, color: '#e3eaf0' }}>
{exp.minimum_additional_evidence}
</Typography>
<Box sx={{ display: 'flex', gap: 3, mt: 1 }}>
<Typography variant="caption" sx={{ color: '#8a949e' }}>
Current effective n: <b>{exp.power.effective_n.toLocaleString()}</b>
</Typography>
<Typography variant="caption" sx={{ color: '#8a949e' }}>
Needed for the {exp.power.practical_margin_pp.toFixed(1)}pp margin:{' '}
<b>{exp.power.needed_effective_n.toLocaleString()}</b>
</Typography>
</Box>
</SectionCard>
<SectionCard title="Recommended experiments — ranked by information gain / cost">
<Box component="table" sx={tableSx}>
<thead>
<tr>
<th>#</th><th>Design</th><th>Instantiated for this investigation</th>
<th>Discriminates</th><th>Cost</th><th>Gain</th><th>Priority</th>
</tr>
</thead>
<tbody>
{exp.experiments.map((d) => (
<tr key={d.id}>
<td style={{ fontWeight: 800 }}>{d.rank}</td>
<td style={{ fontWeight: 700, whiteSpace: 'nowrap' }}>{d.design}</td>
<td><Typography variant="caption" sx={{ color: '#cfd8e0' }}>{d.description}</Typography></td>
<td>
{d.discriminates.map((h) => (
<Chip key={h} size="small" label={h} sx={{ height: 17, fontSize: 9.5, mr: 0.25, mb: 0.25, bgcolor: '#232a31' }} />
))}
</td>
<td>
<Chip size="small" label={`${d.cost} · ~${d.expected_days}d`} sx={{
height: 18, fontSize: 10, fontWeight: 700,
bgcolor: `${COST_COLORS[d.cost]}22`, color: COST_COLORS[d.cost],
}} />
</td>
<td style={{ fontFamily: 'monospace' }}>{d.information_gain.toFixed(1)}</td>
<td style={{ fontFamily: 'monospace', fontWeight: 800 }}>{d.priority.toFixed(1)}</td>
</tr>
))}
</tbody>
</Box>
</SectionCard>
</>
);
}
export function ReportView({ report, inv, reveal, onReveal }: {
report: RcaReport;
inv: Investigation;
reveal: RevealResponse | null;
onReveal: () => void;
}) {
const [showMd, setShowMd] = useState(false);
const ef = report.executive_finding;
const confColor = CONFIDENCE_COLORS[ef.confidence];
return (
<>
<SectionCard
title="Executive finding"
action={
<Box sx={{ display: 'flex', gap: 1 }}>
<Button size="small" startIcon={<FileJson size={14} />} onClick={() => download(`${report.investigation_id}-report.json`, JSON.stringify(report, null, 2), 'application/json')}>
Export JSON
</Button>
<Button size="small" startIcon={<Download size={14} />} onClick={() => download(`${report.investigation_id}-report.md`, report.markdown, 'text/markdown')}>
Export MD
</Button>
</Box>
}
>
<Typography variant="h6" sx={{ fontWeight: 800, fontSize: 17 }}>
{ef.conclusion ?? ef.top_hypothesis}
<Chip size="small" label={`confidence: ${ef.confidence}`} sx={{ ml: 1.5, fontWeight: 800, bgcolor: `${confColor}22`, color: confColor, border: `1px solid ${confColor}55` }} />
</Typography>
<Typography variant="body2" sx={{ color: '#aab4be', mt: 0.5 }}>
{ef.label} — score {ef.score} (gap to runner-up {ef.score_gap_to_runner_up}). Claims under
investigation: offline {report.claims.offline_delta_pp >= 0 ? '+' : ''}{report.claims.offline_delta_pp}pp vs shadow{' '}
{report.claims.shadow_delta_pp}pp on {report.claims.metric}.
</Typography>
</SectionCard>
<SectionCard title="Per-stage summary">
<Box component="table" sx={tableSx}>
<thead>
<tr><th>Stage</th><th>Question</th><th>Verdict</th><th>Mismatches / unknowns</th></tr>
</thead>
<tbody>
{report.stage_summaries.map((s) => (
<tr key={s.stage}>
<td style={{ fontWeight: 600, whiteSpace: 'nowrap' }}>{s.stage}</td>
<td><Typography variant="caption" sx={{ color: '#8a949e' }}>{s.headline}</Typography></td>
<td><StatusPill status={s.verdict === 'clean' ? 'PASS' : s.verdict === 'mismatch' ? 'MISMATCH' : 'UNKNOWN'} /></td>
<td>
<Typography variant="caption">
{[...s.mismatches, ...s.unknowns].join('; ') || '—'}
</Typography>
</td>
</tr>
))}
</tbody>
</Box>
</SectionCard>
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 2 }}>
{(['containment', 'short_term', 'long_term'] as const).map((tier) => (
<SectionCard key={tier} title={tier === 'containment' ? 'Containment (now)' : tier === 'short_term' ? 'Short term' : 'Long term'}>
{report.remediation[tier].map((x) => (
<Typography key={x} variant="body2" sx={{ fontSize: 12, py: 0.25, color: '#cfd8e0' }}>
• {x}
</Typography>
))}
</SectionCard>
))}
</Box>
{report.acknowledged_unknowns.length ? (
<SectionCard title="Unknowns explicitly acknowledged during this investigation">
{report.acknowledged_unknowns.map((e, i) => (
<Typography key={i} variant="caption" sx={{ display: 'block', color: '#ffb74d' }}>
⚠ {e.message} — note: {String((e.data as any)?.note ?? '')}
</Typography>
))}
</SectionCard>
) : null}
<SectionCard
title="Raw markdown"
action={<Button size="small" onClick={() => setShowMd((s) => !s)}>{showMd ? 'hide' : 'show'}</Button>}
>
<Collapse in={showMd}>
<Box component="pre" sx={{ fontSize: 11, fontFamily: 'monospace', whiteSpace: 'pre-wrap', color: '#aab4be', m: 0, maxHeight: 420, overflowY: 'auto' }}>
{report.markdown}
</Box>
</Collapse>
</SectionCard>
{inv.training_mode ? (
<SectionCard title="Training mode — the answer key">
{reveal ? (
<Box sx={{ p: 1.25, borderRadius: 1, border: `1px solid ${BORDER}`, bgcolor: '#0d1116' }}>
<Typography variant="body2" sx={{ fontWeight: 800, color: reveal.cause === ef.top_hypothesis ? '#66bb6a' : '#ef5350' }}>
Planted cause: {reveal.cause}{' '}
{reveal.cause === ef.top_hypothesis ? '— your board got it right ✓' : `— your board's top pick was ${ef.top_hypothesis} ✗`}
</Typography>
<Typography variant="caption" sx={{ color: '#aab4be' }}>{reveal.explanation}</Typography>
</Box>
) : (
<Button variant="outlined" size="small" startIcon={<Eye size={14} />} onClick={onReveal}>
Reveal the planted root cause
</Button>
)}
</SectionCard>
) : null}
</>
);
}
|