refactor(go): split training/store, ucca/rules, ucca_handlers, document_export under 500 LOC

Each of the four oversized files (training/store.go 1569 LOC, ucca/rules.go 1231 LOC,
ucca_handlers.go 1135 LOC, document_export.go 1101 LOC) is split by logical group
into same-package files, all under the 500-line hard cap. Zero behavior changes,
no renamed exported symbols. Also fixed pre-existing hazard_library split (missing
functions and duplicate UUID keys from a prior session).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-04-19 09:29:54 +02:00
parent 3f306fb6f0
commit 9f96061631
36 changed files with 9416 additions and 9365 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,203 @@
package handlers
import (
"net/http"
"github.com/breakpilot/ai-compliance-sdk/internal/ucca"
"github.com/gin-gonic/gin"
)
// ListPatterns returns all available architecture patterns
func (h *UCCAHandlers) ListPatterns(c *gin.Context) {
var response []gin.H
if h.policyEngine != nil {
yamlPatterns := h.policyEngine.GetAllPatterns()
response = make([]gin.H, 0, len(yamlPatterns))
for _, p := range yamlPatterns {
response = append(response, gin.H{
"id": p.ID,
"title": p.Title,
"description": p.Description,
"benefit": p.Benefit,
"effort": p.Effort,
"risk_reduction": p.RiskReduction,
})
}
} else {
patterns := ucca.GetAllPatterns()
response = make([]gin.H, len(patterns))
for i, p := range patterns {
response[i] = gin.H{
"id": p.ID,
"title": p.Title,
"title_de": p.TitleDE,
"description": p.Description,
"description_de": p.DescriptionDE,
"benefits": p.Benefits,
"requirements": p.Requirements,
}
}
}
c.JSON(http.StatusOK, gin.H{"patterns": response})
}
// ListControls returns all available compliance controls
func (h *UCCAHandlers) ListControls(c *gin.Context) {
var response []gin.H
if h.policyEngine != nil {
yamlControls := h.policyEngine.GetAllControls()
response = make([]gin.H, 0, len(yamlControls))
for _, ctrl := range yamlControls {
response = append(response, gin.H{
"id": ctrl.ID,
"title": ctrl.Title,
"description": ctrl.Description,
"gdpr_ref": ctrl.GDPRRef,
"effort": ctrl.Effort,
})
}
} else {
for id, ctrl := range ucca.ControlLibrary {
response = append(response, gin.H{
"id": id,
"title": ctrl.Title,
"description": ctrl.Description,
"severity": ctrl.Severity,
"category": ctrl.Category,
"gdpr_ref": ctrl.GDPRRef,
})
}
}
c.JSON(http.StatusOK, gin.H{"controls": response})
}
// ListProblemSolutions returns all problem-solution mappings
func (h *UCCAHandlers) ListProblemSolutions(c *gin.Context) {
if h.policyEngine == nil {
c.JSON(http.StatusOK, gin.H{
"problem_solutions": []gin.H{},
"message": "Problem-solutions only available with YAML policy engine",
})
return
}
problemSolutions := h.policyEngine.GetProblemSolutions()
response := make([]gin.H, len(problemSolutions))
for i, ps := range problemSolutions {
solutions := make([]gin.H, len(ps.Solutions))
for j, sol := range ps.Solutions {
solutions[j] = gin.H{
"id": sol.ID,
"title": sol.Title,
"pattern": sol.Pattern,
"control": sol.Control,
"removes_problem": sol.RemovesProblem,
"team_question": sol.TeamQuestion,
}
}
triggers := make([]gin.H, len(ps.Triggers))
for j, t := range ps.Triggers {
triggers[j] = gin.H{
"rule": t.Rule,
"without_control": t.WithoutControl,
}
}
response[i] = gin.H{
"problem_id": ps.ProblemID,
"title": ps.Title,
"triggers": triggers,
"solutions": solutions,
}
}
c.JSON(http.StatusOK, gin.H{"problem_solutions": response})
}
// ListExamples returns all available didactic examples
func (h *UCCAHandlers) ListExamples(c *gin.Context) {
examples := ucca.GetAllExamples()
response := make([]gin.H, len(examples))
for i, ex := range examples {
response[i] = gin.H{
"id": ex.ID,
"title": ex.Title,
"title_de": ex.TitleDE,
"description": ex.Description,
"description_de": ex.DescriptionDE,
"domain": ex.Domain,
"outcome": ex.Outcome,
"outcome_de": ex.OutcomeDE,
"lessons": ex.Lessons,
"lessons_de": ex.LessonsDE,
}
}
c.JSON(http.StatusOK, gin.H{"examples": response})
}
// ListRules returns all rules for transparency
func (h *UCCAHandlers) ListRules(c *gin.Context) {
var response []gin.H
var policyVersion string
if h.policyEngine != nil {
yamlRules := h.policyEngine.GetAllRules()
policyVersion = h.policyEngine.GetPolicyVersion()
response = make([]gin.H, len(yamlRules))
for i, r := range yamlRules {
response[i] = gin.H{
"code": r.ID,
"category": r.Category,
"title": r.Title,
"description": r.Description,
"severity": r.Severity,
"gdpr_ref": r.GDPRRef,
"rationale": r.Rationale,
"controls": r.Effect.ControlsAdd,
"patterns": r.Effect.SuggestedPatterns,
"risk_add": r.Effect.RiskAdd,
}
}
} else {
rules := h.legacyRuleEngine.GetRules()
policyVersion = "1.0.0-legacy"
response = make([]gin.H, len(rules))
for i, r := range rules {
response[i] = gin.H{
"code": r.Code,
"category": r.Category,
"title": r.Title,
"title_de": r.TitleDE,
"description": r.Description,
"description_de": r.DescriptionDE,
"severity": r.Severity,
"score_delta": r.ScoreDelta,
"gdpr_ref": r.GDPRRef,
"controls": r.Controls,
"patterns": r.Patterns,
}
}
}
c.JSON(http.StatusOK, gin.H{
"rules": response,
"total": len(response),
"policy_version": policyVersion,
"categories": []string{
"A. Datenklassifikation",
"B. Zweck & Rechtsgrundlage",
"C. Automatisierung",
"D. Training & Modell",
"E. Hosting",
"F. Domain-spezifisch",
"G. Aggregation",
},
})
}

View File

@@ -0,0 +1,243 @@
package handlers
import (
"bytes"
"fmt"
"net/http"
"time"
"github.com/breakpilot/ai-compliance-sdk/internal/llm"
"github.com/breakpilot/ai-compliance-sdk/internal/ucca"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// Explain generates an LLM explanation for an assessment
func (h *UCCAHandlers) Explain(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid ID"})
return
}
var req ucca.ExplainRequest
if err := c.ShouldBindJSON(&req); err != nil {
req.Language = "de"
}
if req.Language == "" {
req.Language = "de"
}
assessment, err := h.store.GetAssessment(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if assessment == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
// Get legal context from RAG
var legalContext *ucca.LegalContext
var legalContextStr string
if h.legalRAGClient != nil {
legalContext, err = h.legalRAGClient.GetLegalContextForAssessment(c.Request.Context(), assessment)
if err != nil {
fmt.Printf("Warning: Could not get legal context: %v\n", err)
} else {
legalContextStr = h.legalRAGClient.FormatLegalContextForPrompt(legalContext)
}
}
prompt := buildExplanationPrompt(assessment, req.Language, legalContextStr)
chatReq := &llm.ChatRequest{
Messages: []llm.Message{
{Role: "system", Content: "Du bist ein Datenschutz-Experte, der DSGVO-Compliance-Bewertungen erklärt. Antworte klar, präzise und auf Deutsch. Beziehe dich auf die angegebenen Rechtsgrundlagen."},
{Role: "user", Content: prompt},
},
MaxTokens: 2000,
Temperature: 0.3,
}
response, err := h.providerRegistry.Chat(c.Request.Context(), chatReq)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "LLM call failed: " + err.Error()})
return
}
explanation := response.Message.Content
model := response.Model
if err := h.store.UpdateExplanation(c.Request.Context(), id, explanation, model); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, ucca.ExplainResponse{
ExplanationText: explanation,
GeneratedAt: time.Now().UTC(),
Model: model,
LegalContext: legalContext,
})
}
// buildExplanationPrompt creates the prompt for the LLM explanation
func buildExplanationPrompt(assessment *ucca.Assessment, language string, legalContext string) string {
var buf bytes.Buffer
buf.WriteString("Erkläre die folgende DSGVO-Compliance-Bewertung für einen KI-Use-Case in verständlicher Sprache:\n\n")
buf.WriteString(fmt.Sprintf("**Ergebnis:** %s\n", assessment.Feasibility))
buf.WriteString(fmt.Sprintf("**Risikostufe:** %s\n", assessment.RiskLevel))
buf.WriteString(fmt.Sprintf("**Risiko-Score:** %d/100\n", assessment.RiskScore))
buf.WriteString(fmt.Sprintf("**Komplexität:** %s\n\n", assessment.Complexity))
if len(assessment.TriggeredRules) > 0 {
buf.WriteString("**Ausgelöste Regeln:**\n")
for _, r := range assessment.TriggeredRules {
buf.WriteString(fmt.Sprintf("- %s (%s): %s\n", r.Code, r.Severity, r.Title))
}
buf.WriteString("\n")
}
if len(assessment.RequiredControls) > 0 {
buf.WriteString("**Erforderliche Maßnahmen:**\n")
for _, ctrl := range assessment.RequiredControls {
buf.WriteString(fmt.Sprintf("- %s: %s\n", ctrl.Title, ctrl.Description))
}
buf.WriteString("\n")
}
if assessment.DSFARecommended {
buf.WriteString("**Hinweis:** Eine Datenschutz-Folgenabschätzung (DSFA) wird empfohlen.\n\n")
}
if assessment.Art22Risk {
buf.WriteString("**Warnung:** Es besteht ein Risiko unter Art. 22 DSGVO (automatisierte Einzelentscheidungen).\n\n")
}
if legalContext != "" {
buf.WriteString(legalContext)
}
buf.WriteString("\nBitte erkläre:\n")
buf.WriteString("1. Warum dieses Ergebnis zustande kam (mit Bezug auf die angegebenen Rechtsgrundlagen)\n")
buf.WriteString("2. Welche konkreten Schritte unternommen werden sollten\n")
buf.WriteString("3. Welche Alternativen es gibt, falls der Use Case abgelehnt wurde\n")
buf.WriteString("4. Welche spezifischen Artikel aus DSGVO/AI Act beachtet werden müssen\n")
return buf.String()
}
// Export exports an assessment as JSON or Markdown
func (h *UCCAHandlers) Export(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid ID"})
return
}
format := c.DefaultQuery("format", "json")
assessment, err := h.store.GetAssessment(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if assessment == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
if format == "md" {
markdown := generateMarkdownExport(assessment)
c.Header("Content-Type", "text/markdown; charset=utf-8")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=ucca_assessment_%s.md", id.String()[:8]))
c.Data(http.StatusOK, "text/markdown; charset=utf-8", []byte(markdown))
return
}
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=ucca_assessment_%s.json", id.String()[:8]))
c.JSON(http.StatusOK, gin.H{
"exported_at": time.Now().UTC().Format(time.RFC3339),
"assessment": assessment,
})
}
// generateMarkdownExport creates a Markdown export of the assessment
func generateMarkdownExport(a *ucca.Assessment) string {
var buf bytes.Buffer
buf.WriteString("# UCCA Use-Case Assessment\n\n")
buf.WriteString(fmt.Sprintf("**ID:** %s\n", a.ID.String()))
buf.WriteString(fmt.Sprintf("**Erstellt:** %s\n", a.CreatedAt.Format("02.01.2006 15:04")))
buf.WriteString(fmt.Sprintf("**Domain:** %s\n\n", a.Domain))
buf.WriteString("## Ergebnis\n\n")
buf.WriteString("| Kriterium | Wert |\n")
buf.WriteString("|-----------|------|\n")
buf.WriteString(fmt.Sprintf("| Machbarkeit | **%s** |\n", a.Feasibility))
buf.WriteString(fmt.Sprintf("| Risikostufe | %s |\n", a.RiskLevel))
buf.WriteString(fmt.Sprintf("| Risiko-Score | %d/100 |\n", a.RiskScore))
buf.WriteString(fmt.Sprintf("| Komplexität | %s |\n", a.Complexity))
buf.WriteString(fmt.Sprintf("| DSFA empfohlen | %t |\n", a.DSFARecommended))
buf.WriteString(fmt.Sprintf("| Art. 22 Risiko | %t |\n", a.Art22Risk))
buf.WriteString(fmt.Sprintf("| Training erlaubt | %s |\n\n", a.TrainingAllowed))
if len(a.TriggeredRules) > 0 {
buf.WriteString("## Ausgelöste Regeln\n\n")
buf.WriteString("| Code | Titel | Schwere | Score |\n")
buf.WriteString("|------|-------|---------|-------|\n")
for _, r := range a.TriggeredRules {
buf.WriteString(fmt.Sprintf("| %s | %s | %s | +%d |\n", r.Code, r.Title, r.Severity, r.ScoreDelta))
}
buf.WriteString("\n")
}
if len(a.RequiredControls) > 0 {
buf.WriteString("## Erforderliche Kontrollen\n\n")
for _, ctrl := range a.RequiredControls {
buf.WriteString(fmt.Sprintf("### %s\n", ctrl.Title))
buf.WriteString(fmt.Sprintf("%s\n\n", ctrl.Description))
if ctrl.GDPRRef != "" {
buf.WriteString(fmt.Sprintf("*Referenz: %s*\n\n", ctrl.GDPRRef))
}
}
}
if len(a.RecommendedArchitecture) > 0 {
buf.WriteString("## Empfohlene Architektur-Patterns\n\n")
for _, p := range a.RecommendedArchitecture {
buf.WriteString(fmt.Sprintf("### %s\n", p.Title))
buf.WriteString(fmt.Sprintf("%s\n\n", p.Description))
}
}
if len(a.ForbiddenPatterns) > 0 {
buf.WriteString("## Verbotene Patterns\n\n")
for _, p := range a.ForbiddenPatterns {
buf.WriteString(fmt.Sprintf("### %s\n", p.Title))
buf.WriteString(fmt.Sprintf("**Grund:** %s\n\n", p.Reason))
}
}
if a.ExplanationText != nil && *a.ExplanationText != "" {
buf.WriteString("## KI-Erklärung\n\n")
buf.WriteString(*a.ExplanationText)
buf.WriteString("\n\n")
}
buf.WriteString("---\n")
buf.WriteString(fmt.Sprintf("*Generiert mit UCCA Policy Version %s*\n", a.PolicyVersion))
return buf.String()
}
// truncateText truncates a string to maxLen characters
func truncateText(text string, maxLen int) string {
if len(text) <= maxLen {
return text
}
return text[:maxLen] + "..."
}

View File

@@ -0,0 +1,280 @@
package handlers
import (
"bytes"
"fmt"
"net/http"
"strings"
"time"
"github.com/breakpilot/ai-compliance-sdk/internal/llm"
"github.com/breakpilot/ai-compliance-sdk/internal/ucca"
"github.com/gin-gonic/gin"
)
// WizardAskRequest represents a question to the Legal Assistant
type WizardAskRequest struct {
Question string `json:"question" binding:"required"`
StepNumber int `json:"step_number"`
FieldID string `json:"field_id,omitempty"`
CurrentData map[string]interface{} `json:"current_data,omitempty"`
}
// WizardAskResponse represents the Legal Assistant response
type WizardAskResponse struct {
Answer string `json:"answer"`
Sources []LegalSource `json:"sources,omitempty"`
RelatedFields []string `json:"related_fields,omitempty"`
GeneratedAt time.Time `json:"generated_at"`
Model string `json:"model"`
}
// LegalSource represents a legal reference used in the answer
type LegalSource struct {
Regulation string `json:"regulation"`
Article string `json:"article,omitempty"`
Text string `json:"text,omitempty"`
}
// AskWizardQuestion handles legal questions from the wizard
func (h *UCCAHandlers) AskWizardQuestion(c *gin.Context) {
var req WizardAskRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
ragQuery := buildWizardRAGQuery(req)
var legalResults []ucca.LegalSearchResult
var sources []LegalSource
if h.legalRAGClient != nil {
results, err := h.legalRAGClient.Search(c.Request.Context(), ragQuery, nil, 5)
if err != nil {
fmt.Printf("Warning: Legal RAG search failed: %v\n", err)
} else {
legalResults = results
sources = make([]LegalSource, len(results))
for i, r := range results {
sources[i] = LegalSource{
Regulation: r.RegulationName,
Article: r.Article,
Text: truncateText(r.Text, 200),
}
}
}
}
prompt := buildWizardAssistantPrompt(req, legalResults)
systemPrompt := buildWizardSystemPrompt(req.StepNumber)
chatReq := &llm.ChatRequest{
Messages: []llm.Message{
{Role: "system", Content: systemPrompt},
{Role: "user", Content: prompt},
},
MaxTokens: 1024,
Temperature: 0.3,
}
response, err := h.providerRegistry.Chat(c.Request.Context(), chatReq)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "LLM call failed: " + err.Error()})
return
}
relatedFields := identifyRelatedFields(req.Question)
c.JSON(http.StatusOK, WizardAskResponse{
Answer: response.Message.Content,
Sources: sources,
RelatedFields: relatedFields,
GeneratedAt: time.Now().UTC(),
Model: response.Model,
})
}
// GetWizardSchema returns the wizard schema for the frontend
func (h *UCCAHandlers) GetWizardSchema(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"version": "1.1",
"total_steps": 8,
"default_mode": "simple",
"legal_assistant": gin.H{
"enabled": true,
"endpoint": "/sdk/v1/ucca/wizard/ask",
"max_tokens": 1024,
"example_questions": []string{
"Was sind personenbezogene Daten?",
"Was ist der Unterschied zwischen AVV und SCC?",
"Brauche ich ein TIA?",
"Was bedeutet Profiling?",
"Was ist Art. 9 DSGVO?",
"Wann brauche ich eine DSFA?",
"Was ist das Data Privacy Framework?",
},
},
"steps": []gin.H{
{"number": 1, "title": "Grundlegende Informationen", "icon": "info"},
{"number": 2, "title": "Welche Daten werden verarbeitet?", "icon": "database"},
{"number": 3, "title": "Wofür wird die KI eingesetzt?", "icon": "target"},
{"number": 4, "title": "Wo läuft die KI?", "icon": "server"},
{"number": 5, "title": "Internationaler Datentransfer", "icon": "globe"},
{"number": 6, "title": "KI-Modell und Training", "icon": "brain"},
{"number": 7, "title": "Verträge & Compliance", "icon": "file-contract"},
{"number": 8, "title": "Automatisierung & Kontrolle", "icon": "user-check"},
},
})
}
// buildWizardRAGQuery creates an optimized query for Legal RAG search
func buildWizardRAGQuery(req WizardAskRequest) string {
query := req.Question
stepContext := map[int]string{
1: "KI-Anwendung Use Case",
2: "personenbezogene Daten Datenkategorien DSGVO Art. 4 Art. 9",
3: "Verarbeitungszweck Profiling Scoring automatisierte Entscheidung Art. 22",
4: "Hosting Cloud On-Premises Auftragsverarbeitung",
5: "Standardvertragsklauseln SCC Drittlandtransfer TIA Transfer Impact Assessment Art. 44 Art. 46",
6: "KI-Modell Training RAG Finetuning",
7: "Auftragsverarbeitungsvertrag AVV DSFA Verarbeitungsverzeichnis Art. 28 Art. 30 Art. 35",
8: "Automatisierung Human-in-the-Loop Art. 22 AI Act",
}
if context, ok := stepContext[req.StepNumber]; ok {
query = query + " " + context
}
return query
}
// buildWizardSystemPrompt creates the system prompt for the Legal Assistant
func buildWizardSystemPrompt(stepNumber int) string {
basePrompt := `Du bist ein freundlicher Rechtsassistent, der Nutzern hilft,
datenschutzrechtliche Begriffe und Anforderungen zu verstehen.
DEINE AUFGABE:
- Erkläre rechtliche Begriffe in einfacher, verständlicher Sprache
- Beantworte Fragen zum aktuellen Wizard-Schritt
- Hilf dem Nutzer, die richtigen Antworten im Wizard zu geben
- Verweise auf relevante Rechtsquellen (DSGVO-Artikel, etc.)
WICHTIGE REGELN:
- Antworte IMMER auf Deutsch
- Verwende einfache Sprache, keine Juristensprache
- Gib konkrete Beispiele wenn möglich
- Bei Unsicherheit empfehle die Rücksprache mit einem Datenschutzbeauftragten
- Du darfst KEINE Rechtsberatung geben, nur erklären
ANTWORT-FORMAT:
- Kurz und prägnant (max. 3-4 Sätze für einfache Fragen)
- Strukturiert mit Aufzählungen bei komplexen Themen
- Immer mit Quellenangabe am Ende (z.B. "Siehe: DSGVO Art. 9")`
stepContexts := map[int]string{
1: "\n\nKONTEXT: Der Nutzer befindet sich im ersten Schritt und gibt grundlegende Informationen zum KI-Vorhaben ein.",
2: "\n\nKONTEXT: Der Nutzer gibt an, welche Datenarten verarbeitet werden. Erkläre die Unterschiede zwischen personenbezogenen Daten, Art. 9 Daten (besondere Kategorien), biometrischen Daten, etc.",
3: "\n\nKONTEXT: Der Nutzer gibt den Verarbeitungszweck an. Erkläre Begriffe wie Profiling, Scoring, systematische Überwachung, automatisierte Entscheidungen mit rechtlicher Wirkung.",
4: "\n\nKONTEXT: Der Nutzer gibt Hosting-Informationen an. Erkläre Cloud vs. On-Premises, wann Drittlandtransfer vorliegt, Unterschiede zwischen EU/EWR und Drittländern.",
5: "\n\nKONTEXT: Der Nutzer beantwortet Fragen zu SCC und TIA. Erkläre Standardvertragsklauseln (SCC), Transfer Impact Assessment (TIA), das Data Privacy Framework (DPF), und wann welche Instrumente erforderlich sind.",
6: "\n\nKONTEXT: Der Nutzer gibt KI-Modell-Informationen an. Erkläre RAG vs. Training/Finetuning, warum Training mit personenbezogenen Daten problematisch ist, und welche Opt-Out-Klauseln wichtig sind.",
7: "\n\nKONTEXT: Der Nutzer beantwortet Fragen zu Verträgen. Erkläre den Auftragsverarbeitungsvertrag (AVV), die Datenschutz-Folgenabschätzung (DSFA), das Verarbeitungsverzeichnis (VVT), und wann diese erforderlich sind.",
8: "\n\nKONTEXT: Der Nutzer gibt den Automatisierungsgrad an. Erkläre Human-in-the-Loop, Art. 22 DSGVO (automatisierte Einzelentscheidungen), und die Anforderungen des AI Acts.",
}
if context, ok := stepContexts[stepNumber]; ok {
basePrompt += context
}
return basePrompt
}
// buildWizardAssistantPrompt creates the user prompt with legal context
func buildWizardAssistantPrompt(req WizardAskRequest, legalResults []ucca.LegalSearchResult) string {
var buf bytes.Buffer
buf.WriteString(fmt.Sprintf("FRAGE DES NUTZERS:\n%s\n\n", req.Question))
if len(legalResults) > 0 {
buf.WriteString("RELEVANTE RECHTSGRUNDLAGEN (aus unserer Bibliothek):\n\n")
for i, result := range legalResults {
buf.WriteString(fmt.Sprintf("%d. %s", i+1, result.RegulationName))
if result.Article != "" {
buf.WriteString(fmt.Sprintf(" - Art. %s", result.Article))
if result.Paragraph != "" {
buf.WriteString(fmt.Sprintf(" Abs. %s", result.Paragraph))
}
}
buf.WriteString("\n")
buf.WriteString(fmt.Sprintf(" %s\n\n", truncateText(result.Text, 300)))
}
}
if req.FieldID != "" {
buf.WriteString(fmt.Sprintf("AKTUELLES FELD: %s\n\n", req.FieldID))
}
buf.WriteString("Bitte beantworte die Frage kurz und verständlich. Verwende die angegebenen Rechtsgrundlagen als Referenz.")
return buf.String()
}
// identifyRelatedFields identifies wizard fields related to the question
func identifyRelatedFields(question string) []string {
question = strings.ToLower(question)
var related []string
keywordMapping := map[string][]string{
"personenbezogen": {"data_types.personal_data"},
"art. 9": {"data_types.article_9_data"},
"sensibel": {"data_types.article_9_data"},
"gesundheit": {"data_types.article_9_data"},
"minderjährig": {"data_types.minor_data"},
"kinder": {"data_types.minor_data"},
"biometrisch": {"data_types.biometric_data"},
"gesicht": {"data_types.biometric_data"},
"kennzeichen": {"data_types.license_plates"},
"standort": {"data_types.location_data"},
"gps": {"data_types.location_data"},
"profiling": {"purpose.profiling"},
"scoring": {"purpose.evaluation_scoring"},
"überwachung": {"processing.systematic_monitoring"},
"automatisch": {"outputs.decision_with_legal_effect", "automation"},
"entscheidung": {"outputs.decision_with_legal_effect"},
"cloud": {"hosting.type", "hosting.region"},
"on-premises": {"hosting.type"},
"lokal": {"hosting.type"},
"scc": {"contracts.scc.present", "contracts.scc.version"},
"standardvertrags": {"contracts.scc.present"},
"drittland": {"hosting.region", "provider.location"},
"usa": {"hosting.region", "provider.location", "provider.dpf_certified"},
"transfer": {"hosting.region", "contracts.tia.present"},
"tia": {"contracts.tia.present", "contracts.tia.result"},
"dpf": {"provider.dpf_certified"},
"data privacy": {"provider.dpf_certified"},
"avv": {"contracts.avv.present"},
"auftragsverarbeitung": {"contracts.avv.present"},
"dsfa": {"governance.dsfa_completed"},
"folgenabschätzung": {"governance.dsfa_completed"},
"verarbeitungsverzeichnis": {"governance.vvt_entry"},
"training": {"model_usage.training", "provider.uses_data_for_training"},
"finetuning": {"model_usage.training"},
"rag": {"model_usage.rag"},
"human": {"processing.human_oversight"},
"aufsicht": {"processing.human_oversight"},
}
seen := make(map[string]bool)
for keyword, fields := range keywordMapping {
if strings.Contains(question, keyword) {
for _, field := range fields {
if !seen[field] {
related = append(related, field)
seen[field] = true
}
}
}
}
return related
}