Files
property-match/src/components/ai-monitoring/AIMonitoringMetrics.tsx
T
Benjamin Sutter 71f4b1eeb6 feat: F019 AI monitoring layer — output transparency & governance workspace
Full 2-panel workspace at /ops/ai-monitoring: metrics strip (total, failed,
pending, approval rate, top prompt version, active model), filterable output
table by type/status/error, and detail panel with output preview, prompt
versioning, error details, and role-aware review actions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 13:11:35 +02:00

52 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Box, Typography } from '@mui/material'
import type { AIOutput } from '../../domain/aiOutput'
interface Props {
outputs: AIOutput[]
}
function MetricCell({ label, value, color }: { label: string; value: string | number; color?: string }) {
return (
<Box sx={{ px: 2, py: 1.25, borderRight: '1px solid #e2e8f0', '&:last-child': { borderRight: 'none' }, minWidth: 0, flex: 1 }}>
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', whiteSpace: 'nowrap', mb: 0.25 }}>
{label}
</Typography>
<Typography
variant="subtitle2"
sx={{ fontWeight: 700, fontSize: '0.9375rem', color: color ?? '#1e293b', lineHeight: 1.2 }}
>
{value}
</Typography>
</Box>
)
}
function mostCommon(arr: string[]): string {
if (!arr.length) return ''
const freq = arr.reduce<Record<string, number>>((acc, v) => ({ ...acc, [v]: (acc[v] ?? 0) + 1 }), {})
return Object.entries(freq).sort((a, b) => b[1] - a[1])[0][0]
}
export function AIMonitoringMetrics({ outputs }: Props) {
const total = outputs.length
const failed = outputs.filter(o => !!o.error).length
const needsReview = outputs.filter(o => o.reviewStatus === 'UNREVIEWED' || o.reviewStatus === 'FLAGGED').length
const approved = outputs.filter(o => o.reviewStatus === 'APPROVED').length
const approvalRate = total > 0 ? Math.round((approved / total) * 100) : 0
const topPrompt = mostCommon(outputs.map(o => o.promptVersion))
const latestModel = outputs.length > 0
? outputs.sort((a, b) => b.createdAt.localeCompare(a.createdAt))[0].model
: ''
return (
<Box sx={{ display: 'flex', bgcolor: 'white', borderBottom: '1px solid #e2e8f0', flexShrink: 0 }}>
<MetricCell label="AI-Outputs total" value={total} />
<MetricCell label="Fehlgeschlagen" value={failed} color={failed > 0 ? '#c0392b' : undefined} />
<MetricCell label="Prüfung ausstehend" value={needsReview} color={needsReview > 0 ? '#d97706' : undefined} />
<MetricCell label="Genehmigungsrate" value={`${approvalRate}%`} color={approvalRate >= 70 ? '#1a7a4a' : '#d97706'} />
<MetricCell label="Häufigste Version" value={topPrompt} />
<MetricCell label="Aktives Modell" value={latestModel.replace('claude-3-5-sonnet-20241022', 'sonnet-3.5').replace('claude-3-haiku-20240307', 'haiku-3').replace('claude-3-opus-20240229', 'opus-3')} />
</Box>
)
}