feat(extraction): POST /compliance/extract-requirements-from-rag
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 34s
CI / test-python-backend-compliance (push) Successful in 31s
CI / test-python-document-crawler (push) Successful in 35s
CI / test-python-dsms-gateway (push) Successful in 17s
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 34s
CI / test-python-backend-compliance (push) Successful in 31s
CI / test-python-document-crawler (push) Successful in 35s
CI / test-python-dsms-gateway (push) Successful in 17s
Sucht alle RAG-Kollektionen nach Prüfaspekten und legt automatisch Anforderungen in der DB an. Kernfeatures: - Durchsucht alle 6 RAG-Kollektionen parallel (bp_compliance_ce, bp_compliance_recht, bp_compliance_gesetze, bp_compliance_datenschutz, bp_dsfa_corpus, bp_legal_templates) - Erkennt BSI Prüfaspekte (O.Purp_6) im Artikel-Feld und per Regex - Dedupliziert nach (regulation_code, article) — safe to call many times - Auto-erstellt Regulations-Stubs für unbekannte regulation_codes - dry_run=true zeigt was erstellt würde ohne DB-Schreibzugriff - Optionale Filter: collections, regulation_codes, search_queries - 18 Tests (alle bestanden) - Frontend: "Aus RAG extrahieren" Button auf /sdk/requirements Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -404,6 +404,50 @@ export default function RequirementsPage() {
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [showAddForm, setShowAddForm] = useState(false)
|
||||
const [expandedId, setExpandedId] = useState<string | null>(null)
|
||||
const [ragExtracting, setRagExtracting] = useState(false)
|
||||
const [ragResult, setRagResult] = useState<{ created: number; skipped_duplicates: number; message: string } | null>(null)
|
||||
|
||||
const extractFromRAG = async () => {
|
||||
setRagExtracting(true)
|
||||
setRagResult(null)
|
||||
try {
|
||||
const res = await fetch('/api/sdk/v1/compliance/extract-requirements-from-rag', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ max_per_query: 20 }),
|
||||
})
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
setRagResult({ created: data.created, skipped_duplicates: data.skipped_duplicates, message: data.message })
|
||||
// Reload requirements list
|
||||
const listRes = await fetch('/api/sdk/v1/compliance/requirements')
|
||||
if (listRes.ok) {
|
||||
const listData = await listRes.json()
|
||||
const reqs = listData.requirements || listData
|
||||
if (Array.isArray(reqs) && reqs.length > 0) {
|
||||
const mapped = reqs.map((r: Record<string, unknown>) => ({
|
||||
id: (r.requirement_id || r.id) as string,
|
||||
regulation: (r.regulation_code || r.regulation || '') as string,
|
||||
article: (r.article || '') as string,
|
||||
title: (r.title || '') as string,
|
||||
description: (r.description || '') as string,
|
||||
criticality: ((r.criticality || r.priority || 'MEDIUM') as string).toUpperCase() as import('@/lib/sdk').RiskSeverity,
|
||||
applicableModules: [] as string[],
|
||||
status: 'NOT_STARTED' as import('@/lib/sdk').RequirementStatus,
|
||||
controls: [] as string[],
|
||||
}))
|
||||
dispatch({ type: 'SET_STATE', payload: { requirements: mapped } })
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setRagResult({ created: 0, skipped_duplicates: 0, message: 'RAG-Extraktion fehlgeschlagen' })
|
||||
}
|
||||
} catch {
|
||||
setRagResult({ created: 0, skipped_duplicates: 0, message: 'RAG-Extraktion nicht erreichbar' })
|
||||
} finally {
|
||||
setRagExtracting(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch requirements from backend on mount
|
||||
useEffect(() => {
|
||||
@@ -626,17 +670,46 @@ export default function RequirementsPage() {
|
||||
explanation={stepInfo.explanation}
|
||||
tips={stepInfo.tips}
|
||||
>
|
||||
<button
|
||||
onClick={() => setShowAddForm(true)}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||||
</svg>
|
||||
Anforderung hinzufuegen
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={extractFromRAG}
|
||||
disabled={ragExtracting}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 disabled:opacity-60 transition-colors"
|
||||
>
|
||||
{ragExtracting ? (
|
||||
<svg className="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.347.347a3.5 3.5 0 01-4.95 0l-.347-.347z" />
|
||||
</svg>
|
||||
)}
|
||||
Aus RAG extrahieren
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowAddForm(true)}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||||
</svg>
|
||||
Anforderung hinzufuegen
|
||||
</button>
|
||||
</div>
|
||||
</StepHeader>
|
||||
|
||||
{/* RAG Extraction Result Banner */}
|
||||
{ragResult && (
|
||||
<div className={`flex items-center justify-between p-3 rounded-lg border ${ragResult.created > 0 ? 'bg-green-50 border-green-200' : 'bg-blue-50 border-blue-200'}`}>
|
||||
<span className="text-sm">
|
||||
{ragResult.created > 0 ? '✅' : 'ℹ️'} {ragResult.message}
|
||||
</span>
|
||||
<button onClick={() => setRagResult(null)} className="text-gray-400 hover:text-gray-600 ml-4">×</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Add Form */}
|
||||
{showAddForm && (
|
||||
<AddRequirementForm
|
||||
|
||||
Reference in New Issue
Block a user