feat: Legal Templates Service — eigene Vorlagen für Dokumentengenerator
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 37s
CI / test-python-backend-compliance (push) Successful in 32s
CI / test-python-document-crawler (push) Successful in 21s
CI / test-python-dsms-gateway (push) Successful in 18s

Implementiert MIT-lizenzierte DSGVO-Templates (DSE, Impressum, AGB) in
der eigenen PostgreSQL-Datenbank statt KLAUSUR_SERVICE-Abhängigkeit.

- Migration 018: compliance_legal_templates Tabelle + 3 Seed-Templates
- Routes: GET/POST/PUT/DELETE /legal-templates + /status + /sources
- Registriert im bestehenden compliance catch-all Proxy (kein neuer Proxy)
- searchTemplates.ts: eigenes Backend als Primary, RAG bleibt Fallback
- ServiceMode-Banner: KLAUSUR_SERVICE-Referenz entfernt
- Tests: 25 Python + 3 Vitest — alle grün

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-03 23:12:07 +01:00
parent 29e6998a28
commit f909182632
8 changed files with 1079 additions and 93 deletions

View File

@@ -1,16 +1,14 @@
/**
* Template search helpers for document-generator.
*
* Provides a two-tier search:
* 1. Primary: KLAUSUR_SERVICE (curated legal templates with full metadata)
* 2. Fallback: RAG proxy (ai-compliance-sdk regulation search)
* Two-tier search:
* 1. Primary: own backend (compliance_legal_templates DB) — MIT-licensed, curated
* 2. Fallback: RAG proxy (ai-compliance-sdk regulation search — law texts)
*/
import type { LegalTemplateResult } from '@/lib/sdk/types'
export const KLAUSUR_SERVICE_URL =
process.env.NEXT_PUBLIC_KLAUSUR_SERVICE_URL || 'http://localhost:8086'
export const TEMPLATES_API = '/api/sdk/v1/compliance/legal-templates'
export const RAG_PROXY = '/api/sdk/v1/rag'
export interface TemplateSearchParams {
@@ -23,63 +21,34 @@ export interface TemplateSearchParams {
}
/**
* Search for legal templates with automatic RAG fallback.
* Search for legal templates.
*
* Tries KLAUSUR_SERVICE first (5 s timeout). If unavailable or returning an
* error, falls back to the RAG proxy served by ai-compliance-sdk.
* Tries own backend DB first (5 s timeout). Falls back to RAG proxy
* (regulation texts — useful as reference, but not ready-made templates).
* Returns an empty array if both services are down.
*/
export async function searchTemplates(
params: TemplateSearchParams
): Promise<LegalTemplateResult[]> {
// 1. Primary: KLAUSUR_SERVICE
// 1. Primary: own backend — compliance_legal_templates table
try {
const res = await fetch(`${KLAUSUR_SERVICE_URL}/api/v1/admin/templates/search`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: params.query,
template_type: params.templateType,
license_types: params.licenseTypes,
language: params.language,
jurisdiction: params.jurisdiction,
limit: params.limit || 10,
}),
signal: AbortSignal.timeout(5000),
})
const url = new URL(TEMPLATES_API, window.location.origin)
if (params.query) url.searchParams.set('query', params.query)
if (params.templateType) url.searchParams.set('document_type', params.templateType)
if (params.language) url.searchParams.set('language', params.language)
url.searchParams.set('limit', String(params.limit || 20))
url.searchParams.set('status', 'published')
const res = await fetch(url.toString(), { signal: AbortSignal.timeout(5000) })
if (res.ok) {
const data = await res.json()
return data.map((r: any) => ({
id: r.id,
score: r.score,
text: r.text,
documentTitle: r.document_title,
templateType: r.template_type,
clauseCategory: r.clause_category,
language: r.language,
jurisdiction: r.jurisdiction,
licenseId: r.license_id,
licenseName: r.license_name,
licenseUrl: r.license_url,
attributionRequired: r.attribution_required,
attributionText: r.attribution_text,
sourceName: r.source_name,
sourceUrl: r.source_url,
sourceRepo: r.source_repo,
placeholders: r.placeholders || [],
isCompleteDocument: r.is_complete_document,
isModular: r.is_modular,
requiresCustomization: r.requires_customization,
outputAllowed: r.output_allowed ?? true,
modificationAllowed: r.modification_allowed ?? true,
distortionProhibited: r.distortion_prohibited ?? false,
}))
return (data.templates || []).map(mapTemplateToResult)
}
} catch {
// KLAUSUR_SERVICE not reachable — fall through to RAG
// Backend not reachable — fall through to RAG
}
// 2. Fallback: RAG proxy
// 2. Fallback: RAG proxy (Gesetzestexte — Referenz, kein fertiges Template)
try {
const res = await fetch(`${RAG_PROXY}/search`, {
method: 'POST',
@@ -122,19 +91,52 @@ export async function searchTemplates(
return []
}
function mapTemplateToResult(r: any): LegalTemplateResult {
return {
id: r.id,
score: 1.0,
text: r.content || '',
documentTitle: r.title,
templateType: r.document_type,
clauseCategory: null,
language: r.language,
jurisdiction: r.jurisdiction,
licenseId: r.license_id as any,
licenseName: r.license_name,
licenseUrl: null,
attributionRequired: r.attribution_required ?? false,
attributionText: null,
sourceName: r.source_name,
sourceUrl: null,
sourceRepo: null,
placeholders: r.placeholders || [],
isCompleteDocument: r.is_complete_document ?? true,
isModular: false,
requiresCustomization: (r.placeholders || []).length > 0,
outputAllowed: true,
modificationAllowed: true,
distortionProhibited: false,
source: 'db' as const,
}
}
export async function getTemplatesStatus(): Promise<any> {
const response = await fetch(`${KLAUSUR_SERVICE_URL}/api/v1/admin/templates/status`, {
signal: AbortSignal.timeout(5000),
})
if (!response.ok) return null
return response.json()
try {
const res = await fetch(`${TEMPLATES_API}/status`, { signal: AbortSignal.timeout(5000) })
if (!res.ok) return null
return res.json()
} catch {
return null
}
}
export async function getSources(): Promise<any[]> {
const response = await fetch(`${KLAUSUR_SERVICE_URL}/api/v1/admin/templates/sources`, {
signal: AbortSignal.timeout(5000),
})
if (!response.ok) return []
const data = await response.json()
return data.sources || []
try {
const res = await fetch(`${TEMPLATES_API}/sources`, { signal: AbortSignal.timeout(5000) })
if (!res.ok) return []
const data = await res.json()
return data.sources || []
} catch {
return []
}
}