feat(compliance-check): scenario badges + extracted profile display
Build + Deploy / build-dsms-node (push) Successful in 13s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / loc-budget (push) Failing after 15s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
Build + Deploy / build-admin-compliance (push) Successful in 1m58s
Build + Deploy / build-backend-compliance (push) Successful in 13s
Build + Deploy / build-ai-sdk (push) Successful in 49s
Build + Deploy / build-developer-portal (push) Successful in 14s
Build + Deploy / build-tts (push) Successful in 15s
Build + Deploy / build-document-crawler (push) Successful in 12s
Build + Deploy / build-dsms-gateway (push) Successful in 11s
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m40s
CI / test-python-dsms-gateway (push) Successful in 24s
CI / validate-canonical-controls (push) Successful in 14s
CI / test-go (push) Successful in 43s
CI / test-python-backend (push) Successful in 39s
CI / test-python-document-crawler (push) Successful in 27s
Build + Deploy / trigger-orca (push) Successful in 2m34s

- Show extracted profile fields (company name, legal form, address,
  DPO, USt-IdNr) with "In Company Profile uebernehmen" button
- Show Compliance Scope hints extracted from documents
- Scenario badges per document: Neugenerierung (red), Korrekturen
  (amber), Konform (green)
- Summary line shows scenario counts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-12 17:49:45 +02:00
parent e785b6d695
commit 08fcb5f239
2 changed files with 69 additions and 6 deletions
@@ -24,6 +24,13 @@ interface DocResult {
checks: CheckItem[] checks: CheckItem[]
findings_count: number findings_count: number
error: string error: string
scenario?: string // regenerate | fix | import | skip
}
const SCENARIO_LABELS: Record<string, { label: string; color: string; bg: string }> = {
regenerate: { label: 'Neugenerierung', color: 'text-red-700', bg: 'bg-red-100' },
fix: { label: 'Korrekturen', color: 'text-amber-700', bg: 'bg-amber-100' },
import: { label: 'Konform', color: 'text-green-700', bg: 'bg-green-100' },
} }
const DOC_TYPE_LABELS: Record<string, string> = { const DOC_TYPE_LABELS: Record<string, string> = {
@@ -91,14 +98,23 @@ export function ChecklistView({ results }: { results: DocResult[] }) {
if (!results || results.length === 0) return null if (!results || results.length === 0) return null
const totalOk = results.filter(r => r.completeness_pct === 100).length const scenarioCounts = {
regenerate: results.filter(r => r.scenario === 'regenerate').length,
fix: results.filter(r => r.scenario === 'fix').length,
import: results.filter(r => r.scenario === 'import').length,
}
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between flex-wrap gap-2">
<h3 className="text-sm font-semibold text-gray-800"> <h3 className="text-sm font-semibold text-gray-800">
Dokumenten-Pruefung ({results.length} Dokumente, {totalOk} vollstaendig) Dokumenten-Pruefung ({results.length} Dokumente)
</h3> </h3>
<div className="flex gap-2 text-[10px]">
{scenarioCounts.import > 0 && <span className="bg-green-100 text-green-700 px-2 py-0.5 rounded-full">{scenarioCounts.import} konform</span>}
{scenarioCounts.fix > 0 && <span className="bg-amber-100 text-amber-700 px-2 py-0.5 rounded-full">{scenarioCounts.fix} Korrekturen</span>}
{scenarioCounts.regenerate > 0 && <span className="bg-red-100 text-red-700 px-2 py-0.5 rounded-full">{scenarioCounts.regenerate} Neugenerierung</span>}
</div>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
@@ -131,7 +147,14 @@ export function ChecklistView({ results }: { results: DocResult[] }) {
{typeLabel} {typeLabel}
</span> </span>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="text-sm font-medium text-gray-900 truncate">{r.label}</div> <div className="text-sm font-medium text-gray-900 truncate flex items-center gap-2">
{r.label}
{r.scenario && SCENARIO_LABELS[r.scenario] && (
<span className={`text-[10px] px-1.5 py-0.5 rounded-full font-medium ${SCENARIO_LABELS[r.scenario].bg} ${SCENARIO_LABELS[r.scenario].color}`}>
{SCENARIO_LABELS[r.scenario].label}
</span>
)}
</div>
<div className="text-xs text-gray-500 truncate"> <div className="text-xs text-gray-500 truncate">
{l1Checks.length > 0 {l1Checks.length > 0
? `${l1Passed}/${l1Scoreable.length} Pflichtangaben` ? `${l1Passed}/${l1Scoreable.length} Pflichtangaben`
@@ -364,6 +364,46 @@ export function ComplianceCheckTab() {
</div> </div>
)} )}
{/* Extracted Profile — pre-fill suggestion */}
{results.extracted_profile?.company_profile && Object.keys(results.extracted_profile.company_profile).length > 0 && (
<div className="mb-4 p-3 bg-emerald-50 border border-emerald-200 rounded-lg text-xs">
<div className="flex items-center justify-between mb-1">
<span className="font-semibold text-emerald-900">Aus Dokumenten extrahiert</span>
<button className="text-emerald-700 hover:text-emerald-900 text-xs font-medium underline"
onClick={() => { /* TODO: navigate to company profile with pre-fill */ }}>
In Company Profile uebernehmen
</button>
</div>
<div className="flex flex-wrap gap-x-4 gap-y-1 text-emerald-700">
{results.extracted_profile.company_profile.companyName && (
<span>Firma: <strong>{results.extracted_profile.company_profile.companyName}</strong></span>
)}
{results.extracted_profile.company_profile.legalForm && (
<span>Rechtsform: {results.extracted_profile.company_profile.legalForm.toUpperCase()}</span>
)}
{results.extracted_profile.company_profile.headquartersCity && (
<span>Sitz: {results.extracted_profile.company_profile.headquartersZip} {results.extracted_profile.company_profile.headquartersCity}</span>
)}
{results.extracted_profile.company_profile.dpoEmail && (
<span>DSB: {results.extracted_profile.company_profile.dpoEmail}</span>
)}
{results.extracted_profile.company_profile.ustIdNr && (
<span>USt-IdNr: {results.extracted_profile.company_profile.ustIdNr}</span>
)}
</div>
{results.extracted_profile.compliance_scope_hints?.length > 0 && (
<div className="mt-2 pt-2 border-t border-emerald-200 text-emerald-600">
<span className="font-medium">Scope-Hinweise: </span>
{results.extracted_profile.compliance_scope_hints.map((h: any, i: number) => (
<span key={i} className="inline-block bg-emerald-100 rounded px-1.5 py-0.5 mr-1 mb-1">
{h.source}
</span>
))}
</div>
)}
</div>
)}
{/* Banner Check Result */} {/* Banner Check Result */}
{results.banner_result && ( {results.banner_result && (
<div className={`mb-4 p-3 rounded-lg border text-xs ${ <div className={`mb-4 p-3 rounded-lg border text-xs ${
@@ -387,8 +427,8 @@ export function ComplianceCheckTab() {
<> <>
Banner erkannt{results.banner_result.provider ? ` (${results.banner_result.provider})` : ''}. Banner erkannt{results.banner_result.provider ? ` (${results.banner_result.provider})` : ''}.
{results.banner_result.violations > 0 {results.banner_result.violations > 0
? ` ${results.banner_result.violations} Auffaelligkeit${results.banner_result.violations !== 1 ? 'en' : ''} gefunden — Cross-Check-Ergebnisse sind in der Cookie-Richtlinie-Checkliste enthalten.` ? ` ${results.banner_result.violations} Auffaelligkeit${results.banner_result.violations !== 1 ? 'en' : ''} gefunden.`
: ' Keine Auffaelligkeiten beim Banner-Cookie-Abgleich.'} : ' Keine Auffaelligkeiten.'}
</> </>
) : ( ) : (
'Kein Cookie-Banner erkannt oder Banner-Check nicht moeglich.' 'Kein Cookie-Banner erkannt oder Banner-Check nicht moeglich.'