feat(governance): surface adr100 slo states
All checks were successful
Code Review / ai-code-review (push) Successful in 11s
CD Pipeline / tests (push) Successful in 1m0s
CD Pipeline / build-and-deploy (push) Successful in 4m0s
CD Pipeline / post-deploy-checks (push) Successful in 1m55s

This commit is contained in:
Your Name
2026-05-14 19:57:32 +08:00
parent 6c16a7b162
commit 809bc9670b
7 changed files with 559 additions and 40 deletions

View File

@@ -31,11 +31,32 @@ const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''
// =============================================================================
interface SloApiResponse {
metrics?: {
metrics?: Array<{
name: SloMetric['name']
value: number | null
threshold: number
direction: 'above' | 'below'
sample_count: number
violated: boolean
}> | {
decision_accuracy?: { current: number; target: number; status: string; sparkline?: number[] }
km_growth_rate?: { current: number; target: number; status: string; sparkline?: number[] }
mcp_call_diversity?: { current: number; target: number; status: string; sparkline?: number[] }
}
adr100?: {
overall_status?: string
overall_compliance?: number | null
metrics?: Array<{
name: SloMetric['name']
value: number | null
target: number
status: 'ok' | 'warning' | 'violated' | 'skipped_low_volume' | 'no_data' | 'error'
unit: 'percent' | 'count'
sample_count?: number | null
window?: string
reason?: string | null
}>
}
overall_compliance?: number
computed_at?: string
}
@@ -51,15 +72,55 @@ interface SummaryApiResponse {
// =============================================================================
function mapStatus(s: string): SloMetric['status'] {
if (s === 'healthy') return 'healthy'
if (s === 'healthy' || s === 'ok') return 'healthy'
if (s === 'warning') return 'warning'
if (s === 'skipped_low_volume') return 'syncing'
if (s === 'no_data') return 'idle'
return 'critical'
}
function buildMetrics(api: SloApiResponse): SloMetric[] {
const adr100Metrics = api.adr100?.metrics
if (adr100Metrics?.length) {
const order: SloMetric['name'][] = ['autonomy_rate', 'decision_accuracy', 'confidence_calibration', 'km_growth_rate']
const byName = new Map(adr100Metrics.map(metric => [metric.name, metric]))
const built: SloMetric[] = []
order.forEach(name => {
const entry = byName.get(name)
if (!entry) return
built.push({
name,
current: entry.value ?? null,
target: entry.target,
status: mapStatus(entry.status),
state: entry.status,
unit: entry.unit === 'count' ? 'count' : '%',
sparkline: [],
sampleCount: entry.sample_count ?? null,
window: entry.window,
reason: entry.reason ?? null,
})
})
return built
}
if (Array.isArray(api.metrics)) {
return api.metrics.map(entry => ({
name: entry.name,
current: entry.value,
target: entry.threshold,
status: entry.value == null ? 'syncing' : entry.violated ? 'critical' : 'healthy',
state: entry.value == null ? 'skipped_low_volume' : entry.violated ? 'violated' : 'ok',
unit: '%',
sparkline: [],
sampleCount: entry.sample_count,
}))
}
const m = api.metrics ?? {}
const names: Array<SloMetric['name']> = ['decision_accuracy', 'km_growth_rate', 'mcp_call_diversity']
return names.map(name => {
if (Array.isArray(m)) return []
const names: Array<'decision_accuracy' | 'km_growth_rate' | 'mcp_call_diversity'> = ['decision_accuracy', 'km_growth_rate', 'mcp_call_diversity']
return names.map((name): SloMetric => {
const entry = m[name]
return {
name,
@@ -111,7 +172,7 @@ export function SloTab() {
}, [])
const metrics = sloData ? buildMetrics(sloData) : []
const compliance = sloData?.overall_compliance ?? null
const compliance = sloData?.adr100?.overall_compliance ?? sloData?.overall_compliance ?? null
const chartData: ViolationDataPoint[] = summaryData?.data ?? []
const eventTypes: string[] = summaryData?.event_types ?? []
@@ -169,7 +230,7 @@ export function SloTab() {
className="slo-kpi-grid"
>
{sloLoading
? [0, 1, 2].map(i => <SloKpiCard key={i} metric={{ name: 'decision_accuracy', current: null, target: 0.9, status: 'warning' }} loading />)
? [0, 1, 2, 3].map(i => <SloKpiCard key={i} metric={{ name: 'decision_accuracy', current: null, target: 0.9, status: 'warning' }} loading />)
: metrics.map(m => <SloKpiCard key={m.name} metric={m} />)
}
</div>