feat(agent): Audit-Kopf + 4 KPI-Kacheln ueber den Ergebnis-Tabs

ResultSummary: Titel (Firma aus extracted_profile) + check_id + 4 Kacheln
(Dokumente, Konform, Offene Pflichtangaben, Zu pruefen), gerechnet aus
result.results. Co-Pilot-Ton: gruen/gelb/rot nur bei echten Werten.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-06-10 23:53:12 +02:00
parent 3f23a64d5f
commit 5da20af4fd
3 changed files with 123 additions and 0 deletions
@@ -13,6 +13,7 @@ import React, { useState } from 'react'
import { ChecklistView, DOC_TYPE_LABELS, type DocResult } from './ChecklistView' import { ChecklistView, DOC_TYPE_LABELS, type DocResult } from './ChecklistView'
import { DocResultView } from './DocResultView' import { DocResultView } from './DocResultView'
import { MigrationPanel } from './MigrationPanel' import { MigrationPanel } from './MigrationPanel'
import { ResultSummary } from './ResultSummary'
export function ComplianceResultTabs({ results }: { results: any }) { export function ComplianceResultTabs({ results }: { results: any }) {
// Themen-Tabs aus der HAUPT-Engine (result.results) — nicht aus dem // Themen-Tabs aus der HAUPT-Engine (result.results) — nicht aus dem
@@ -23,6 +24,9 @@ export function ComplianceResultTabs({ results }: { results: any }) {
return ( return (
<div className="bg-white border border-gray-200 rounded-xl p-6 shadow-sm space-y-4"> <div className="bg-white border border-gray-200 rounded-xl p-6 shadow-sm space-y-4">
{/* Audit-Kopf: Titel + check_id + 4 KPI-Kacheln */}
<ResultSummary results={results} />
{/* Kopf-Boxen über den Tabs */} {/* Kopf-Boxen über den Tabs */}
{results.business_profile && ( {results.business_profile && (
<div className="p-3 bg-blue-50 border border-blue-200 rounded-lg text-xs"> <div className="p-3 bg-blue-50 border border-blue-200 rounded-lg text-xs">
@@ -0,0 +1,89 @@
'use client'
/**
* ResultSummary — Audit-Kopf: Titel + check_id + 4 KPI-Kacheln über den
* Dokument-Tabs. Co-Pilot-Ton (grün wenn gut, rot nur bei echten offenen
* Punkten, gelb für „zu prüfen"). Rechnet aus result.results (Haupt-Engine).
*/
import React from 'react'
import type { CheckItem, DocResult } from './ChecklistView'
type Tone = 'gray' | 'green' | 'red' | 'amber'
const TONE: Record<Tone, string> = {
gray: 'text-gray-800',
green: 'text-green-700',
red: 'text-red-700',
amber: 'text-amber-700',
}
function Tile({ label, value, tone }: { label: string; value: React.ReactNode; tone: Tone }) {
return (
<div className="border border-gray-200 rounded-lg p-3 bg-white">
<div className={`text-2xl font-semibold leading-none ${TONE[tone]}`}>{value}</div>
<div className="text-xs text-gray-500 mt-1.5">{label}</div>
</div>
)
}
function isReview(c: CheckItem): boolean {
return c.severity === 'INFO' && !c.passed && !c.skipped
}
export function ResultSummary({ results }: { results: any }) {
const docs: DocResult[] = results.results || []
const company = results.extracted_profile?.company_profile?.companyName as string | undefined
let offen = 0
let zuPruefen = 0
let konform = 0
let checked = 0
for (const d of docs) {
if (d.error) continue
checked++
const l1Score = d.checks.filter(c => (c.level ?? 1) === 1 && c.severity !== 'INFO')
const l1Failed = l1Score.filter(c => !c.passed).length
const l2Failed = d.checks.filter(
c => (c.level ?? 1) === 2 && !c.skipped && !c.passed && c.severity !== 'INFO',
).length
offen += l1Failed + l2Failed
zuPruefen += d.checks.filter(isReview).length
if (l1Failed === 0 && (d.completeness_pct ?? 0) === 100) konform++
}
return (
<div className="space-y-3">
<div>
<h2 className="text-lg font-semibold text-gray-900">
Compliance-Check{company ? `: ${company}` : ''}
</h2>
<p className="text-xs text-gray-500 mt-0.5">
{results.check_id && (
<>ID <code className="bg-gray-100 px-1 rounded">{results.check_id}</code> · </>
)}
{docs.length} Dokument{docs.length !== 1 ? 'e' : ''} geprüft
</p>
</div>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
<Tile label="Dokumente" value={docs.length} tone="gray" />
<Tile
label="Konform"
value={`${konform}/${checked || docs.length}`}
tone={checked > 0 && konform === checked ? 'green' : 'gray'}
/>
<Tile
label="Offene Pflichtangaben"
value={offen}
tone={offen > 0 ? 'red' : 'green'}
/>
<Tile
label="Zu prüfen"
value={zuPruefen}
tone={zuPruefen > 0 ? 'amber' : 'gray'}
/>
</div>
</div>
)
}
@@ -0,0 +1,30 @@
import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import { ResultSummary } from '../ResultSummary'
describe('ResultSummary', () => {
it('zeigt Firma im Titel + zählt Konform-KPI aus result.results', () => {
const results = {
check_id: 'abc123',
extracted_profile: { company_profile: { companyName: 'Bayerische Motoren Werke Aktiengesellschaft' } },
results: [
{ doc_type: 'impressum', completeness_pct: 100, correctness_pct: 100, error: '',
checks: [{ id: 'a', label: 'X', passed: true, severity: 'HIGH', matched_text: '', level: 1 }] },
{ doc_type: 'dse', completeness_pct: 50, correctness_pct: 50, error: '',
checks: [
{ id: 'b', label: 'Y', passed: false, severity: 'HIGH', matched_text: '', level: 1 },
{ id: 'c', label: 'Z', passed: false, severity: 'INFO', matched_text: '', level: 1 },
] },
],
}
render(<ResultSummary results={results} />)
expect(screen.getByText(/Bayerische Motoren Werke/)).toBeInTheDocument()
// 4 Kachel-Labels + Konform 1/2 (impressum konform, dse nicht)
expect(screen.getByText('Dokumente')).toBeInTheDocument()
expect(screen.getByText('Konform')).toBeInTheDocument()
expect(screen.getByText('Offene Pflichtangaben')).toBeInTheDocument()
expect(screen.getByText('Zu prüfen')).toBeInTheDocument()
expect(screen.getByText('1/2')).toBeInTheDocument()
})
})