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>
143 lines
4.2 KiB
TypeScript
143 lines
4.2 KiB
TypeScript
/**
|
|
* Template search helpers for document-generator.
|
|
*
|
|
* 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 TEMPLATES_API = '/api/sdk/v1/compliance/legal-templates'
|
|
export const RAG_PROXY = '/api/sdk/v1/rag'
|
|
|
|
export interface TemplateSearchParams {
|
|
query: string
|
|
templateType?: string
|
|
licenseTypes?: string[]
|
|
language?: 'de' | 'en'
|
|
jurisdiction?: string
|
|
limit?: number
|
|
}
|
|
|
|
/**
|
|
* Search for legal templates.
|
|
*
|
|
* 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: own backend — compliance_legal_templates table
|
|
try {
|
|
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.templates || []).map(mapTemplateToResult)
|
|
}
|
|
} catch {
|
|
// Backend not reachable — fall through to RAG
|
|
}
|
|
|
|
// 2. Fallback: RAG proxy (Gesetzestexte — Referenz, kein fertiges Template)
|
|
try {
|
|
const res = await fetch(`${RAG_PROXY}/search`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ query: params.query || '', limit: params.limit || 10 }),
|
|
})
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
return (data.results || []).map((r: any, i: number) => ({
|
|
id: r.regulation_code || `rag-${i}`,
|
|
score: r.score ?? 0.5,
|
|
text: r.text || '',
|
|
documentTitle: r.regulation_name || r.regulation_short || 'Dokument',
|
|
templateType: 'regulation',
|
|
clauseCategory: null,
|
|
language: 'de',
|
|
jurisdiction: 'eu',
|
|
licenseId: null,
|
|
licenseName: null,
|
|
licenseUrl: null,
|
|
attributionRequired: false,
|
|
attributionText: null,
|
|
sourceName: 'RAG',
|
|
sourceUrl: null,
|
|
sourceRepo: null,
|
|
placeholders: [],
|
|
isCompleteDocument: false,
|
|
isModular: true,
|
|
requiresCustomization: true,
|
|
outputAllowed: true,
|
|
modificationAllowed: true,
|
|
distortionProhibited: false,
|
|
source: 'rag' as const,
|
|
}))
|
|
}
|
|
} catch {
|
|
// both services failed
|
|
}
|
|
|
|
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> {
|
|
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[]> {
|
|
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 []
|
|
}
|
|
}
|