feat(agent): Impressum-Tab auf Haupt-Engine + Profil/§36-Fixes

Ergebnis-Tab rendert jetzt result.results (Haupt-Doc-Check) statt des
abweichenden v3-Agenten — BMW korrekt statt False Positives:
- DocResultView: ein Dokument als Pflichtangaben-Tabelle (Label + gefundener
  Text + 3-Tier-Status), KEINE MC-IDs. ComplianceResultTabs speist Tabs aus
  result.results; ChecklistView-Bausteine exportiert + wiederverwendet.
- profile_extractor: Firmenname/Rechtsform = fruehester Treffer + ausge-
  schriebene Formen (Aktiengesellschaft) -> BMW AG statt "juris GmbH".
- 36 VSBG (MC-010): reines b2c -> POSSIBLY_APPLICABLE (Pruef-Hinweis) statt
  MEDIUM-FAIL; hart nur bei ecommerce. possibly_hint pro MC.
- McCoverage traegt label + found (Snippet); mc_possibly-Aggregat.
- AgentFindingCard/Methodik: interne check_id/mc_id nicht mehr angezeigt.

Tests: test_four_status (16) + Frontend-Vitest gruen; CI-Suite 206, v3/GT
unveraendert. Nur eigene Dateien (geteilter Tree).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-06-10 23:44:01 +02:00
parent a7dc12f30f
commit 3f23a64d5f
15 changed files with 450 additions and 187 deletions
@@ -0,0 +1,82 @@
'use client'
/**
* AgentPflichtTable — die geprüften Pflichtangaben als menschliche Tabelle:
* Status-Icon + Feldname + tatsächlich gefundener Text. Ersetzt die alte
* MC-ID-Liste.
*
* WICHTIG: zeigt NIE die mc_id (Reverse-Engineering-Schutz der MC-Bibliothek)
* — nur das menschliche `label`. Generisch für jeden Agenten verwendbar.
*/
import React from 'react'
import type { McCoverage } from './_agentTypes'
const DISP: Record<string, { icon: string; text: string; color: string }> = {
ok: { icon: '✓', text: 'vorhanden', color: '#16a34a' },
high: { icon: '✗', text: 'fehlt', color: '#dc2626' },
medium: { icon: '✗', text: 'fehlt', color: '#d97706' },
low: { icon: '✗', text: 'fehlt', color: '#2563eb' },
possibly_applicable: { icon: '?', text: 'zu prüfen', color: '#ca8a04' },
insufficient_evidence: { icon: '?', text: 'unklar', color: '#64748b' },
na: { icon: '', text: 'nicht anwendbar', color: '#94a3b8' },
skipped: { icon: '', text: 'nicht geprüft', color: '#cbd5e1' },
}
// Reihenfolge: Probleme zuerst, dann erfüllt, dann n/a.
const RANK: Record<string, number> = {
high: 0, medium: 1, low: 2, possibly_applicable: 3,
insufficient_evidence: 4, ok: 5, na: 6, skipped: 7,
}
export function AgentPflichtTable({ coverage }: { coverage: McCoverage[] }) {
if (!coverage?.length) return null
const rows = [...coverage].sort(
(a, b) => (RANK[a.status] ?? 9) - (RANK[b.status] ?? 9),
)
const count = (s: string) => coverage.filter(c => c.status === s).length
const ok = count('ok')
const fehlt = count('high') + count('medium') + count('low')
const pruefen = count('possibly_applicable') + count('insufficient_evidence')
const na = count('na') + count('skipped')
return (
<div className="border rounded overflow-hidden">
<div className="px-3 py-2 text-xs font-semibold uppercase text-gray-700 border-b bg-slate-50">
Pflichtangaben <span className="text-green-700">{ok} vorhanden</span>
{fehlt > 0 && <> · <span className="text-red-600">{fehlt} fehlt</span></>}
{pruefen > 0 && (
<> · <span className="text-yellow-700">{pruefen} zu prüfen</span></>
)}
{na > 0 && <> · <span className="text-gray-400">{na} n/a</span></>}
</div>
<div className="divide-y divide-gray-100">
{rows.map((c, i) => {
const d = DISP[c.status] || DISP.skipped
return (
<div key={i} className="flex items-start gap-2 px-3 py-1.5 text-xs">
<span
className="font-bold w-4 text-center shrink-0"
style={{ color: d.color }}
aria-label={d.text}
>
{d.icon}
</span>
<span className="font-medium text-gray-800 w-52 shrink-0">
{c.label || 'Angabe'}
</span>
<span className="text-gray-500 flex-1 min-w-0 break-words">
{c.status === 'ok' ? (
<span className="italic">{c.found || 'vorhanden'}</span>
) : (
<span style={{ color: d.color }}>{d.text}</span>
)}
</span>
</div>
)
})}
</div>
</div>
)
}