feat: Use-Cases/UCCA Module auf 100% — Interface Fix, Search/Offset/Total, Explain/Export, Edit-Mode
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 35s
CI / test-python-backend-compliance (push) Successful in 33s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 18s

Kritische Bug Fixes:
- [id]/page.tsx: FullAssessment Interface repariert (nested result → flat fields)
- resultForCard baut explizit aus flachen Assessment-Feldern (feasibility, risk_score etc.)
- Use-Case-Text-Pfad: assessment.intake?.use_case_text statt assessment.use_case_text
- rule_code/code Mapping beim Übergeben an AssessmentResultCard

Backend (A2+A3):
- store.go: AssessmentFilters um Search + Offset erweitert
- ListAssessments: COUNT-Query (total), ILIKE-Search auf title, OFFSET-Pagination
- ListAssessments Signatur: ([]Assessment, int, error)
- Handler: search/offset aus Query-Params, total in Response
- import "strconv" hinzugefügt

Neue Features:
- KI-Erklärung Button (POST /explain) mit lila Erklärungsbox
- Export-Buttons Markdown + JSON (Download-Links)
- Edit-Mode in new/page.tsx: useSearchParams(?edit=id), Form vorausfüllen
- Bedingte PUT/POST Logik; nach Edit → Detail-Seite Redirect
- Suspense-Wrapper für useSearchParams (Next.js 15 Requirement)

Backend Edit:
- store.go: UpdateAssessment() Methode (UPDATE-Query)
- ucca_handlers.go: UpdateAssessment Handler (re-evaluiert Intake)
- main.go: PUT /ucca/assessments/:id Route registriert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-03 15:13:42 +01:00
parent d4845adea7
commit 312c2c9b60
5 changed files with 417 additions and 104 deletions

View File

@@ -3,6 +3,7 @@ package ucca
import (
"context"
"encoding/json"
"strconv"
"time"
"github.com/google/uuid"
@@ -137,7 +138,40 @@ func (s *Store) GetAssessment(ctx context.Context, id uuid.UUID) (*Assessment, e
}
// ListAssessments lists assessments for a tenant with optional filters
func (s *Store) ListAssessments(ctx context.Context, tenantID uuid.UUID, filters *AssessmentFilters) ([]Assessment, error) {
func (s *Store) ListAssessments(ctx context.Context, tenantID uuid.UUID, filters *AssessmentFilters) ([]Assessment, int, error) {
baseWhere := " WHERE tenant_id = $1"
args := []interface{}{tenantID}
argIdx := 2
// Build WHERE clause from filters
if filters != nil {
if filters.Feasibility != "" {
baseWhere += " AND feasibility = $" + itoa(argIdx)
args = append(args, filters.Feasibility)
argIdx++
}
if filters.Domain != "" {
baseWhere += " AND domain = $" + itoa(argIdx)
args = append(args, filters.Domain)
argIdx++
}
if filters.RiskLevel != "" {
baseWhere += " AND risk_level = $" + itoa(argIdx)
args = append(args, filters.RiskLevel)
argIdx++
}
if filters.Search != "" {
baseWhere += " AND title ILIKE $" + strconv.Itoa(argIdx)
args = append(args, "%"+filters.Search+"%")
argIdx++
}
}
// COUNT query (same WHERE, no ORDER/LIMIT/OFFSET)
var total int
s.pool.QueryRow(ctx, "SELECT COUNT(*) FROM ucca_assessments"+baseWhere, args...).Scan(&total)
// Data query
query := `
SELECT
id, tenant_id, namespace_id, title, policy_version, status,
@@ -149,41 +183,25 @@ func (s *Store) ListAssessments(ctx context.Context, tenantID uuid.UUID, filters
corpus_version_id, corpus_version,
explanation_text, explanation_generated_at, explanation_model,
domain, created_at, updated_at, created_by
FROM ucca_assessments WHERE tenant_id = $1`
FROM ucca_assessments` + baseWhere + " ORDER BY created_at DESC"
args := []interface{}{tenantID}
argIdx := 2
// Apply filters
if filters != nil {
if filters.Feasibility != "" {
query += " AND feasibility = $" + itoa(argIdx)
args = append(args, filters.Feasibility)
argIdx++
}
if filters.Domain != "" {
query += " AND domain = $" + itoa(argIdx)
args = append(args, filters.Domain)
argIdx++
}
if filters.RiskLevel != "" {
query += " AND risk_level = $" + itoa(argIdx)
args = append(args, filters.RiskLevel)
argIdx++
}
// Apply LIMIT
if filters != nil && filters.Limit > 0 {
query += " LIMIT $" + strconv.Itoa(argIdx)
args = append(args, filters.Limit)
argIdx++
}
query += " ORDER BY created_at DESC"
// Apply limit
if filters != nil && filters.Limit > 0 {
query += " LIMIT $" + itoa(argIdx)
args = append(args, filters.Limit)
// Apply OFFSET
if filters != nil && filters.Offset > 0 {
query += " OFFSET $" + strconv.Itoa(argIdx)
args = append(args, filters.Offset)
argIdx++
}
rows, err := s.pool.Query(ctx, query, args...)
if err != nil {
return nil, err
return nil, 0, err
}
defer rows.Close()
@@ -205,7 +223,7 @@ func (s *Store) ListAssessments(ctx context.Context, tenantID uuid.UUID, filters
&domain, &a.CreatedAt, &a.UpdatedAt, &a.CreatedBy,
)
if err != nil {
return nil, err
return nil, 0, err
}
// Unmarshal JSONB fields
@@ -226,7 +244,7 @@ func (s *Store) ListAssessments(ctx context.Context, tenantID uuid.UUID, filters
assessments = append(assessments, a)
}
return assessments, nil
return assessments, total, nil
}
// DeleteAssessment deletes an assessment by ID
@@ -235,6 +253,34 @@ func (s *Store) DeleteAssessment(ctx context.Context, id uuid.UUID) error {
return err
}
// UpdateAssessment updates an existing assessment's intake and re-evaluated results
func (s *Store) UpdateAssessment(ctx context.Context, id uuid.UUID, a *Assessment) error {
intake, _ := json.Marshal(a.Intake)
triggeredRules, _ := json.Marshal(a.TriggeredRules)
requiredControls, _ := json.Marshal(a.RequiredControls)
recommendedArchitecture, _ := json.Marshal(a.RecommendedArchitecture)
forbiddenPatterns, _ := json.Marshal(a.ForbiddenPatterns)
exampleMatches, _ := json.Marshal(a.ExampleMatches)
now := time.Now().UTC()
_, err := s.pool.Exec(ctx, `
UPDATE ucca_assessments SET
title = $2, intake = $3, use_case_text_stored = $4, use_case_text_hash = $5,
feasibility = $6, risk_level = $7, complexity = $8, risk_score = $9,
triggered_rules = $10, required_controls = $11, recommended_architecture = $12,
forbidden_patterns = $13, example_matches = $14,
dsfa_recommended = $15, art22_risk = $16, training_allowed = $17,
domain = $18, policy_version = $19, updated_at = $20
WHERE id = $1
`, id, a.Title, intake, a.UseCaseTextStored, a.UseCaseTextHash,
string(a.Feasibility), string(a.RiskLevel), string(a.Complexity), a.RiskScore,
triggeredRules, requiredControls, recommendedArchitecture,
forbiddenPatterns, exampleMatches,
a.DSFARecommended, a.Art22Risk, string(a.TrainingAllowed),
string(a.Domain), a.PolicyVersion, now)
return err
}
// UpdateExplanation updates the LLM explanation for an assessment
func (s *Store) UpdateExplanation(ctx context.Context, id uuid.UUID, explanation string, model string) error {
now := time.Now().UTC()
@@ -307,7 +353,9 @@ type AssessmentFilters struct {
Feasibility string
Domain string
RiskLevel string
Search string // ILIKE on title
Limit int
Offset int // OFFSET for pagination
}
// ============================================================================