diff --git a/admin-compliance/app/sdk/agent/_components/ComplianceResultTabs.tsx b/admin-compliance/app/sdk/agent/_components/ComplianceResultTabs.tsx
index 2ed00cd8..31a12522 100644
--- a/admin-compliance/app/sdk/agent/_components/ComplianceResultTabs.tsx
+++ b/admin-compliance/app/sdk/agent/_components/ComplianceResultTabs.tsx
@@ -13,6 +13,7 @@ import React, { useState } from 'react'
import { ChecklistView, DOC_TYPE_LABELS, type DocResult } from './ChecklistView'
import { DocResultView } from './DocResultView'
import { MigrationPanel } from './MigrationPanel'
+import { RemediationPlan } from './RemediationPlan'
import { ResultSummary } from './ResultSummary'
export function ComplianceResultTabs({ results }: { results: any }) {
@@ -147,6 +148,9 @@ export function ComplianceResultTabs({ results }: { results: any }) {
) : null}
+ {/* Abstellmaßnahmen + Ticket-Formulierung (Übergabe an anderes Team) */}
+
+
{/* Check-Footer (themenübergreifend) */}
{results.email_status && (
diff --git a/admin-compliance/app/sdk/agent/_components/RemediationPlan.tsx b/admin-compliance/app/sdk/agent/_components/RemediationPlan.tsx
new file mode 100644
index 00000000..501eb79c
--- /dev/null
+++ b/admin-compliance/app/sdk/agent/_components/RemediationPlan.tsx
@@ -0,0 +1,140 @@
+'use client'
+
+/**
+ * RemediationPlan — Abstellmaßnahmen + Ticket-Formulierung.
+ *
+ * Aus den offenen Punkten (result.results, Haupt-Engine) je Finding eine
+ * Maßnahme + einen fertigen Ticket-Text ableiten und übergabebereit machen
+ * (Kopieren / JSON-Export). SCOPE: BreakPilot formuliert NUR — Ticketsystem,
+ * Jira-Sync und Feedback-Loop baut ein anderes Team. Keine zweite Engine.
+ */
+
+import React, { useState } from 'react'
+
+import { DOC_TYPE_LABELS, type DocResult } from './ChecklistView'
+
+type Priority = 'Hoch' | 'Mittel' | 'Niedrig'
+
+interface Remediation {
+ docType: string
+ docLabel: string
+ checkLabel: string
+ action: string
+ ticketTitle: string
+ ticketBody: string
+ priority: Priority
+}
+
+const PRIO_RANK: Record
= { Hoch: 0, Mittel: 1, Niedrig: 2 }
+const PRIO_COLOR: Record = {
+ Hoch: 'bg-red-100 text-red-700',
+ Mittel: 'bg-amber-100 text-amber-700',
+ Niedrig: 'bg-blue-100 text-blue-700',
+}
+
+function toPriority(sev: string): Priority {
+ const s = (sev || '').toUpperCase()
+ if (s === 'HIGH' || s === 'CRITICAL') return 'Hoch'
+ if (s === 'MEDIUM') return 'Mittel'
+ return 'Niedrig'
+}
+
+function buildRemediations(docs: DocResult[]): Remediation[] {
+ const out: Remediation[] = []
+ for (const d of docs) {
+ if (d.error) continue
+ const docLabel = DOC_TYPE_LABELS[d.doc_type] || d.doc_type
+ const failed = d.checks.filter(
+ c => !c.passed && !c.skipped && c.severity !== 'INFO',
+ )
+ for (const c of failed) {
+ const action = c.hint || `${c.label} im ${docLabel} ergänzen.`
+ out.push({
+ docType: d.doc_type,
+ docLabel,
+ checkLabel: c.label,
+ action,
+ ticketTitle: `Compliance: ${docLabel} – ${c.label}`,
+ ticketBody:
+ `Dokument: ${docLabel}\nPrüfpunkt: ${c.label}\n` +
+ `Status: nicht erfüllt\nMaßnahme: ${action}`,
+ priority: toPriority(c.severity),
+ })
+ }
+ }
+ return out.sort((a, b) => PRIO_RANK[a.priority] - PRIO_RANK[b.priority])
+}
+
+export function RemediationPlan({ results }: { results: any }) {
+ const items = buildRemediations(results.results || [])
+ const [copied, setCopied] = useState(null)
+
+ if (items.length === 0) {
+ return (
+
+ Keine offenen Pflichtangaben — kein Handlungsbedarf.
+
+ )
+ }
+
+ function copyTicket(i: number, body: string) {
+ navigator.clipboard?.writeText(body)
+ setCopied(i)
+ window.setTimeout(() => setCopied(null), 1500)
+ }
+
+ function exportAll() {
+ const payload = items.map(it => ({
+ title: it.ticketTitle,
+ body: it.ticketBody,
+ priority: it.priority,
+ doc_type: it.docType,
+ }))
+ const blob = new Blob([JSON.stringify(payload, null, 2)], {
+ type: 'application/json',
+ })
+ const url = URL.createObjectURL(blob)
+ const a = document.createElement('a')
+ a.href = url
+ a.download = 'breakpilot-tickets.json'
+ a.click()
+ URL.revokeObjectURL(url)
+ }
+
+ return (
+
+
+
+ Abstellmaßnahmen & Tickets ({items.length})
+
+
+
+
+ {items.map((it, i) => (
+
+
+
+ {it.priority}
+
+
+ {it.docLabel}: {it.checkLabel}
+
+
+
{it.action}
+
+
+ ))}
+
+
+ )
+}
diff --git a/admin-compliance/app/sdk/agent/_components/__tests__/RemediationPlan.test.tsx b/admin-compliance/app/sdk/agent/_components/__tests__/RemediationPlan.test.tsx
new file mode 100644
index 00000000..e8f58549
--- /dev/null
+++ b/admin-compliance/app/sdk/agent/_components/__tests__/RemediationPlan.test.tsx
@@ -0,0 +1,38 @@
+import { describe, it, expect } from 'vitest'
+import { render, screen } from '@testing-library/react'
+
+import { RemediationPlan } from '../RemediationPlan'
+
+describe('RemediationPlan', () => {
+ it('leitet Maßnahmen nur aus echten offenen Punkten ab', () => {
+ const results = {
+ results: [
+ { doc_type: 'impressum', error: '', completeness_pct: 50, checks: [
+ { id: 'a', label: 'Registernummer', passed: false, severity: 'HIGH', matched_text: '', level: 1, hint: 'HRB ergänzen' },
+ { id: 'b', label: 'Telefon', passed: false, severity: 'MEDIUM', matched_text: '', level: 1 },
+ { id: 'c', label: 'OK-Feld', passed: true, severity: 'HIGH', matched_text: 'x', level: 1 },
+ { id: 'd', label: 'Info-Hinweis', passed: false, severity: 'INFO', matched_text: '', level: 1 },
+ ] },
+ ],
+ }
+ render()
+ // 2 Maßnahmen (HIGH + MEDIUM); OK + INFO ausgeschlossen
+ expect(screen.getByText(/Abstellmaßnahmen & Tickets \(2\)/)).toBeInTheDocument()
+ expect(screen.getByText(/Registernummer/)).toBeInTheDocument()
+ expect(screen.getByText('HRB ergänzen')).toBeInTheDocument() // hint = Maßnahme
+ expect(screen.queryByText(/Info-Hinweis/)).not.toBeInTheDocument()
+ expect(screen.queryByText(/OK-Feld/)).not.toBeInTheDocument()
+ })
+
+ it('zeigt Erfolg, wenn keine offenen Punkte', () => {
+ const results = {
+ results: [
+ { doc_type: 'impressum', error: '', completeness_pct: 100, checks: [
+ { id: 'a', label: 'X', passed: true, severity: 'HIGH', matched_text: 'x', level: 1 },
+ ] },
+ ],
+ }
+ render()
+ expect(screen.getByText(/kein Handlungsbedarf/)).toBeInTheDocument()
+ })
+})