diff --git a/ai-compliance-sdk/internal/ucca/escalation_store.go b/ai-compliance-sdk/internal/ucca/escalation_store.go index ad0cd41..a7ac613 100644 --- a/ai-compliance-sdk/internal/ucca/escalation_store.go +++ b/ai-compliance-sdk/internal/ucca/escalation_store.go @@ -387,116 +387,3 @@ func (s *EscalationStore) GetEscalationStats(ctx context.Context, tenantID uuid. return stats, nil } -// DSB Pool Operations - -// AddDSBPoolMember adds a member to the DSB review pool. -func (s *EscalationStore) AddDSBPoolMember(ctx context.Context, m *DSBPoolMember) error { - query := ` - INSERT INTO ucca_dsb_pool ( - id, tenant_id, user_id, user_name, user_email, role, - is_active, max_concurrent_reviews, created_at, updated_at - ) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, NOW(), NOW() - ) - ON CONFLICT (tenant_id, user_id) DO UPDATE - SET user_name = $4, user_email = $5, role = $6, - is_active = $7, max_concurrent_reviews = $8, updated_at = NOW() - ` - - if m.ID == uuid.Nil { - m.ID = uuid.New() - } - - _, err := s.pool.Exec(ctx, query, - m.ID, m.TenantID, m.UserID, m.UserName, m.UserEmail, m.Role, - m.IsActive, m.MaxConcurrentReviews, - ) - - return err -} - -// GetDSBPoolMembers retrieves active DSB pool members for a tenant. -func (s *EscalationStore) GetDSBPoolMembers(ctx context.Context, tenantID uuid.UUID, role string) ([]DSBPoolMember, error) { - query := ` - SELECT id, tenant_id, user_id, user_name, user_email, role, - is_active, max_concurrent_reviews, current_reviews, created_at, updated_at - FROM ucca_dsb_pool - WHERE tenant_id = $1 AND is_active = true - ` - args := []interface{}{tenantID} - - if role != "" { - query += " AND role = $2" - args = append(args, role) - } - - query += " ORDER BY current_reviews ASC, user_name ASC" - - rows, err := s.pool.Query(ctx, query, args...) - if err != nil { - return nil, err - } - defer rows.Close() - - var members []DSBPoolMember - for rows.Next() { - var m DSBPoolMember - err := rows.Scan( - &m.ID, &m.TenantID, &m.UserID, &m.UserName, &m.UserEmail, &m.Role, - &m.IsActive, &m.MaxConcurrentReviews, &m.CurrentReviews, &m.CreatedAt, &m.UpdatedAt, - ) - if err != nil { - return nil, err - } - members = append(members, m) - } - - return members, nil -} - -// GetNextAvailableReviewer finds the next available reviewer for a role. -func (s *EscalationStore) GetNextAvailableReviewer(ctx context.Context, tenantID uuid.UUID, role string) (*DSBPoolMember, error) { - query := ` - SELECT id, tenant_id, user_id, user_name, user_email, role, - is_active, max_concurrent_reviews, current_reviews, created_at, updated_at - FROM ucca_dsb_pool - WHERE tenant_id = $1 AND is_active = true AND role = $2 - AND current_reviews < max_concurrent_reviews - ORDER BY current_reviews ASC - LIMIT 1 - ` - - var m DSBPoolMember - err := s.pool.QueryRow(ctx, query, tenantID, role).Scan( - &m.ID, &m.TenantID, &m.UserID, &m.UserName, &m.UserEmail, &m.Role, - &m.IsActive, &m.MaxConcurrentReviews, &m.CurrentReviews, &m.CreatedAt, &m.UpdatedAt, - ) - - if err != nil { - return nil, err - } - - return &m, nil -} - -// IncrementReviewerCount increments the current review count for a DSB member. -func (s *EscalationStore) IncrementReviewerCount(ctx context.Context, userID uuid.UUID) error { - query := ` - UPDATE ucca_dsb_pool - SET current_reviews = current_reviews + 1, updated_at = NOW() - WHERE user_id = $1 - ` - _, err := s.pool.Exec(ctx, query, userID) - return err -} - -// DecrementReviewerCount decrements the current review count for a DSB member. -func (s *EscalationStore) DecrementReviewerCount(ctx context.Context, userID uuid.UUID) error { - query := ` - UPDATE ucca_dsb_pool - SET current_reviews = GREATEST(0, current_reviews - 1), updated_at = NOW() - WHERE user_id = $1 - ` - _, err := s.pool.Exec(ctx, query, userID) - return err -} diff --git a/ai-compliance-sdk/internal/ucca/escalation_store_dsb.go b/ai-compliance-sdk/internal/ucca/escalation_store_dsb.go new file mode 100644 index 0000000..870b023 --- /dev/null +++ b/ai-compliance-sdk/internal/ucca/escalation_store_dsb.go @@ -0,0 +1,123 @@ +package ucca + +import ( + "context" + + "github.com/google/uuid" +) + +// ============================================================================ +// DSB Pool Operations +// ============================================================================ + +// AddDSBPoolMember adds a member to the DSB review pool. +func (s *EscalationStore) AddDSBPoolMember(ctx context.Context, m *DSBPoolMember) error { + query := ` + INSERT INTO ucca_dsb_pool ( + id, tenant_id, user_id, user_name, user_email, role, + is_active, max_concurrent_reviews, created_at, updated_at + ) VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, NOW(), NOW() + ) + ON CONFLICT (tenant_id, user_id) DO UPDATE + SET user_name = $4, user_email = $5, role = $6, + is_active = $7, max_concurrent_reviews = $8, updated_at = NOW() + ` + + if m.ID == uuid.Nil { + m.ID = uuid.New() + } + + _, err := s.pool.Exec(ctx, query, + m.ID, m.TenantID, m.UserID, m.UserName, m.UserEmail, m.Role, + m.IsActive, m.MaxConcurrentReviews, + ) + + return err +} + +// GetDSBPoolMembers retrieves active DSB pool members for a tenant. +func (s *EscalationStore) GetDSBPoolMembers(ctx context.Context, tenantID uuid.UUID, role string) ([]DSBPoolMember, error) { + query := ` + SELECT id, tenant_id, user_id, user_name, user_email, role, + is_active, max_concurrent_reviews, current_reviews, created_at, updated_at + FROM ucca_dsb_pool + WHERE tenant_id = $1 AND is_active = true + ` + args := []interface{}{tenantID} + + if role != "" { + query += " AND role = $2" + args = append(args, role) + } + + query += " ORDER BY current_reviews ASC, user_name ASC" + + rows, err := s.pool.Query(ctx, query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + var members []DSBPoolMember + for rows.Next() { + var m DSBPoolMember + err := rows.Scan( + &m.ID, &m.TenantID, &m.UserID, &m.UserName, &m.UserEmail, &m.Role, + &m.IsActive, &m.MaxConcurrentReviews, &m.CurrentReviews, &m.CreatedAt, &m.UpdatedAt, + ) + if err != nil { + return nil, err + } + members = append(members, m) + } + + return members, nil +} + +// GetNextAvailableReviewer finds the next available reviewer for a role. +func (s *EscalationStore) GetNextAvailableReviewer(ctx context.Context, tenantID uuid.UUID, role string) (*DSBPoolMember, error) { + query := ` + SELECT id, tenant_id, user_id, user_name, user_email, role, + is_active, max_concurrent_reviews, current_reviews, created_at, updated_at + FROM ucca_dsb_pool + WHERE tenant_id = $1 AND is_active = true AND role = $2 + AND current_reviews < max_concurrent_reviews + ORDER BY current_reviews ASC + LIMIT 1 + ` + + var m DSBPoolMember + err := s.pool.QueryRow(ctx, query, tenantID, role).Scan( + &m.ID, &m.TenantID, &m.UserID, &m.UserName, &m.UserEmail, &m.Role, + &m.IsActive, &m.MaxConcurrentReviews, &m.CurrentReviews, &m.CreatedAt, &m.UpdatedAt, + ) + + if err != nil { + return nil, err + } + + return &m, nil +} + +// IncrementReviewerCount increments the current review count for a DSB member. +func (s *EscalationStore) IncrementReviewerCount(ctx context.Context, userID uuid.UUID) error { + query := ` + UPDATE ucca_dsb_pool + SET current_reviews = current_reviews + 1, updated_at = NOW() + WHERE user_id = $1 + ` + _, err := s.pool.Exec(ctx, query, userID) + return err +} + +// DecrementReviewerCount decrements the current review count for a DSB member. +func (s *EscalationStore) DecrementReviewerCount(ctx context.Context, userID uuid.UUID) error { + query := ` + UPDATE ucca_dsb_pool + SET current_reviews = GREATEST(0, current_reviews - 1), updated_at = NOW() + WHERE user_id = $1 + ` + _, err := s.pool.Exec(ctx, query, userID) + return err +} diff --git a/ai-compliance-sdk/internal/ucca/license_policy.go b/ai-compliance-sdk/internal/ucca/license_policy.go index dfd0eab..31b7314 100644 --- a/ai-compliance-sdk/internal/ucca/license_policy.go +++ b/ai-compliance-sdk/internal/ucca/license_policy.go @@ -11,66 +11,6 @@ import ( // Handles license/copyright compliance for standards and norms // ============================================================================= -// LicensedContentFacts represents the license-related facts from the wizard -type LicensedContentFacts struct { - Present bool `json:"present"` - Publisher string `json:"publisher"` // DIN_MEDIA, VDI, VDE, ISO, etc. - LicenseType string `json:"license_type"` // SINGLE_WORKSTATION, NETWORK_INTRANET, etc. - AIUsePermitted string `json:"ai_use_permitted"` // YES, NO, UNKNOWN - ProofUploaded bool `json:"proof_uploaded"` - OperationMode string `json:"operation_mode"` // LINK_ONLY, NOTES_ONLY, FULLTEXT_RAG, TRAINING - DistributionScope string `json:"distribution_scope"` // SINGLE_USER, COMPANY_INTERNAL, etc. - ContentType string `json:"content_type"` // NORM_FULLTEXT, CUSTOMER_NOTES, etc. -} - -// LicensePolicyResult represents the evaluation result -type LicensePolicyResult struct { - Allowed bool `json:"allowed"` - EffectiveMode string `json:"effective_mode"` // The mode that will actually be used - Reason string `json:"reason"` - Gaps []LicenseGap `json:"gaps"` - RequiredControls []LicenseControl `json:"required_controls"` - StopLine *LicenseStopLine `json:"stop_line,omitempty"` // If hard blocked - OutputRestrictions *OutputRestrictions `json:"output_restrictions"` - EscalationLevel string `json:"escalation_level"` - RiskScore int `json:"risk_score"` -} - -// LicenseGap represents a license-related gap -type LicenseGap struct { - ID string `json:"id"` - Title string `json:"title"` - Description string `json:"description"` - Controls []string `json:"controls"` - Severity string `json:"severity"` -} - -// LicenseControl represents a required control for license compliance -type LicenseControl struct { - ID string `json:"id"` - Title string `json:"title"` - Description string `json:"description"` - WhatToDo string `json:"what_to_do"` - Evidence []string `json:"evidence_needed"` -} - -// LicenseStopLine represents a hard block -type LicenseStopLine struct { - ID string `json:"id"` - Title string `json:"title"` - Message string `json:"message"` - Outcome string `json:"outcome"` // NOT_ALLOWED, NOT_ALLOWED_UNTIL_LICENSE_CLEARED -} - -// OutputRestrictions defines how outputs should be filtered -type OutputRestrictions struct { - AllowQuotes bool `json:"allow_quotes"` - MaxQuoteLength int `json:"max_quote_length"` // in characters - RequireCitation bool `json:"require_citation"` - AllowCopy bool `json:"allow_copy"` - AllowExport bool `json:"allow_export"` -} - // LicensePolicyEngine evaluates license compliance type LicensePolicyEngine struct { // Configuration can be added here @@ -144,7 +84,6 @@ func (e *LicensePolicyEngine) evaluateLinkOnlyMode(facts *LicensedContentFacts, result.Reason = "Link-only Modus ist ohne spezielle Lizenz erlaubt" result.RiskScore = 0 - // Very restrictive output result.OutputRestrictions = &OutputRestrictions{ AllowQuotes: false, MaxQuoteLength: 0, @@ -153,7 +92,6 @@ func (e *LicensePolicyEngine) evaluateLinkOnlyMode(facts *LicensedContentFacts, AllowExport: false, } - // Recommend control for proper setup result.RequiredControls = append(result.RequiredControls, LicenseControl{ ID: "CTRL-LINK-ONLY-MODE", Title: "Link-only / Evidence Navigator aktivieren", @@ -170,7 +108,6 @@ func (e *LicensePolicyEngine) evaluateNotesOnlyMode(facts *LicensedContentFacts, result.Reason = "Notes-only Modus mit kundeneigenen Zusammenfassungen" result.RiskScore = 10 - // Allow paraphrased content result.OutputRestrictions = &OutputRestrictions{ AllowQuotes: false, // No direct quotes from norms MaxQuoteLength: 0, @@ -244,7 +181,6 @@ func (e *LicensePolicyEngine) evaluateExcerptOnlyMode(facts *LicensedContentFact func (e *LicensePolicyEngine) evaluateFulltextRAGMode(facts *LicensedContentFacts, result *LicensePolicyResult) { result.RiskScore = 60 - // Check if AI use is explicitly permitted AND proof is uploaded if facts.AIUsePermitted == "YES" && facts.ProofUploaded { result.EffectiveMode = "FULLTEXT_RAG" result.Allowed = true @@ -290,7 +226,6 @@ func (e *LicensePolicyEngine) evaluateFulltextRAGMode(facts *LicensedContentFact result.EscalationLevel = "E3" - // Set stop line result.StopLine = &LicenseStopLine{ ID: "STOP_FULLTEXT_WITHOUT_PROOF", Title: "Volltext-RAG blockiert", @@ -312,7 +247,6 @@ func (e *LicensePolicyEngine) evaluateFulltextRAGMode(facts *LicensedContentFact func (e *LicensePolicyEngine) evaluateTrainingMode(facts *LicensedContentFacts, result *LicensePolicyResult) { result.RiskScore = 80 - // Training is almost always blocked for standards if facts.AIUsePermitted == "YES" && facts.ProofUploaded && facts.LicenseType == "AI_LICENSE" { result.EffectiveMode = "TRAINING" result.Allowed = true @@ -353,10 +287,8 @@ func (e *LicensePolicyEngine) evaluateTrainingMode(facts *LicensedContentFacts, // applyPublisherRestrictions applies publisher-specific rules func (e *LicensePolicyEngine) applyPublisherRestrictions(facts *LicensedContentFacts, result *LicensePolicyResult) { - // DIN Media specific restrictions if facts.Publisher == "DIN_MEDIA" { if facts.AIUsePermitted != "YES" { - // DIN Media explicitly prohibits AI use without license if facts.OperationMode == "FULLTEXT_RAG" || facts.OperationMode == "TRAINING" { result.Allowed = false result.EffectiveMode = "LINK_ONLY" @@ -394,7 +326,6 @@ func (e *LicensePolicyEngine) applyPublisherRestrictions(facts *LicensedContentF // checkDistributionScope checks if distribution scope matches license type func (e *LicensePolicyEngine) checkDistributionScope(facts *LicensedContentFacts, result *LicensePolicyResult) { - // Single workstation license with broad distribution if facts.LicenseType == "SINGLE_WORKSTATION" { if facts.DistributionScope == "COMPANY_INTERNAL" || facts.DistributionScope == "SUBSIDIARIES" || @@ -413,7 +344,6 @@ func (e *LicensePolicyEngine) checkDistributionScope(facts *LicensedContentFacts } } - // Network license with external distribution if facts.LicenseType == "NETWORK_INTRANET" { if facts.DistributionScope == "EXTERNAL_CUSTOMERS" { result.Gaps = append(result.Gaps, LicenseGap{ @@ -433,16 +363,16 @@ func (e *LicensePolicyEngine) checkDistributionScope(facts *LicensedContentFacts // CanIngestFulltext checks if fulltext ingestion is allowed func (e *LicensePolicyEngine) CanIngestFulltext(facts *LicensedContentFacts) bool { if !facts.Present { - return true // No licensed content, no restrictions + return true } switch facts.OperationMode { case "LINK_ONLY": - return false // Only metadata/references + return false case "NOTES_ONLY": - return false // Only customer notes, not fulltext + return false case "EXCERPT_ONLY": - return false // Only short excerpts + return false case "FULLTEXT_RAG": return facts.AIUsePermitted == "YES" && facts.ProofUploaded case "TRAINING": @@ -457,8 +387,6 @@ func (e *LicensePolicyEngine) CanIngestNotes(facts *LicensedContentFacts) bool { if !facts.Present { return true } - - // Notes are allowed in most modes return facts.OperationMode == "NOTES_ONLY" || facts.OperationMode == "EXCERPT_ONLY" || facts.OperationMode == "FULLTEXT_RAG" || @@ -471,40 +399,17 @@ func (e *LicensePolicyEngine) GetEffectiveMode(facts *LicensedContentFacts) stri return result.EffectiveMode } -// LicenseIngestDecision represents the decision for ingesting a document -type LicenseIngestDecision struct { - AllowFulltext bool `json:"allow_fulltext"` - AllowNotes bool `json:"allow_notes"` - AllowMetadata bool `json:"allow_metadata"` - Reason string `json:"reason"` - EffectiveMode string `json:"effective_mode"` -} - // DecideIngest returns the ingest decision for a document func (e *LicensePolicyEngine) DecideIngest(facts *LicensedContentFacts) *LicenseIngestDecision { result := e.Evaluate(facts) - decision := &LicenseIngestDecision{ + return &LicenseIngestDecision{ AllowMetadata: true, // Metadata is always allowed AllowNotes: e.CanIngestNotes(facts), AllowFulltext: e.CanIngestFulltext(facts), Reason: result.Reason, EffectiveMode: result.EffectiveMode, } - - return decision -} - -// LicenseAuditEntry represents an audit log entry for license decisions -type LicenseAuditEntry struct { - Timestamp time.Time `json:"timestamp"` - TenantID string `json:"tenant_id"` - DocumentID string `json:"document_id,omitempty"` - Facts *LicensedContentFacts `json:"facts"` - Decision string `json:"decision"` // ALLOW, DENY, DOWNGRADE - EffectiveMode string `json:"effective_mode"` - Reason string `json:"reason"` - StopLineID string `json:"stop_line_id,omitempty"` } // FormatAuditEntry creates an audit entry for logging diff --git a/ai-compliance-sdk/internal/ucca/license_policy_types.go b/ai-compliance-sdk/internal/ucca/license_policy_types.go new file mode 100644 index 0000000..0cd05fc --- /dev/null +++ b/ai-compliance-sdk/internal/ucca/license_policy_types.go @@ -0,0 +1,88 @@ +package ucca + +import "time" + +// ============================================================================= +// License Policy Types +// ============================================================================= + +// LicensedContentFacts represents the license-related facts from the wizard +type LicensedContentFacts struct { + Present bool `json:"present"` + Publisher string `json:"publisher"` // DIN_MEDIA, VDI, VDE, ISO, etc. + LicenseType string `json:"license_type"` // SINGLE_WORKSTATION, NETWORK_INTRANET, etc. + AIUsePermitted string `json:"ai_use_permitted"` // YES, NO, UNKNOWN + ProofUploaded bool `json:"proof_uploaded"` + OperationMode string `json:"operation_mode"` // LINK_ONLY, NOTES_ONLY, FULLTEXT_RAG, TRAINING + DistributionScope string `json:"distribution_scope"` // SINGLE_USER, COMPANY_INTERNAL, etc. + ContentType string `json:"content_type"` // NORM_FULLTEXT, CUSTOMER_NOTES, etc. +} + +// LicensePolicyResult represents the evaluation result +type LicensePolicyResult struct { + Allowed bool `json:"allowed"` + EffectiveMode string `json:"effective_mode"` // The mode that will actually be used + Reason string `json:"reason"` + Gaps []LicenseGap `json:"gaps"` + RequiredControls []LicenseControl `json:"required_controls"` + StopLine *LicenseStopLine `json:"stop_line,omitempty"` // If hard blocked + OutputRestrictions *OutputRestrictions `json:"output_restrictions"` + EscalationLevel string `json:"escalation_level"` + RiskScore int `json:"risk_score"` +} + +// LicenseGap represents a license-related gap +type LicenseGap struct { + ID string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + Controls []string `json:"controls"` + Severity string `json:"severity"` +} + +// LicenseControl represents a required control for license compliance +type LicenseControl struct { + ID string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + WhatToDo string `json:"what_to_do"` + Evidence []string `json:"evidence_needed"` +} + +// LicenseStopLine represents a hard block +type LicenseStopLine struct { + ID string `json:"id"` + Title string `json:"title"` + Message string `json:"message"` + Outcome string `json:"outcome"` // NOT_ALLOWED, NOT_ALLOWED_UNTIL_LICENSE_CLEARED +} + +// OutputRestrictions defines how outputs should be filtered +type OutputRestrictions struct { + AllowQuotes bool `json:"allow_quotes"` + MaxQuoteLength int `json:"max_quote_length"` // in characters + RequireCitation bool `json:"require_citation"` + AllowCopy bool `json:"allow_copy"` + AllowExport bool `json:"allow_export"` +} + +// LicenseIngestDecision represents the decision for ingesting a document +type LicenseIngestDecision struct { + AllowFulltext bool `json:"allow_fulltext"` + AllowNotes bool `json:"allow_notes"` + AllowMetadata bool `json:"allow_metadata"` + Reason string `json:"reason"` + EffectiveMode string `json:"effective_mode"` +} + +// LicenseAuditEntry represents an audit log entry for license decisions +type LicenseAuditEntry struct { + Timestamp time.Time `json:"timestamp"` + TenantID string `json:"tenant_id"` + DocumentID string `json:"document_id,omitempty"` + Facts *LicensedContentFacts `json:"facts"` + Decision string `json:"decision"` // ALLOW, DENY, DOWNGRADE + EffectiveMode string `json:"effective_mode"` + Reason string `json:"reason"` + StopLineID string `json:"stop_line_id,omitempty"` +} diff --git a/ai-compliance-sdk/internal/ucca/models.go b/ai-compliance-sdk/internal/ucca/models.go index c1bd46a..f5af98c 100644 --- a/ai-compliance-sdk/internal/ucca/models.go +++ b/ai-compliance-sdk/internal/ucca/models.go @@ -1,11 +1,5 @@ package ucca -import ( - "time" - - "github.com/google/uuid" -) - // ============================================================================ // Constants / Enums // ============================================================================ @@ -53,15 +47,15 @@ type Domain string const ( // Industrie & Produktion - DomainAutomotive Domain = "automotive" + DomainAutomotive Domain = "automotive" DomainMechanicalEngineering Domain = "mechanical_engineering" - DomainPlantEngineering Domain = "plant_engineering" + DomainPlantEngineering Domain = "plant_engineering" DomainElectricalEngineering Domain = "electrical_engineering" - DomainAerospace Domain = "aerospace" - DomainChemicals Domain = "chemicals" - DomainFoodBeverage Domain = "food_beverage" - DomainTextiles Domain = "textiles" - DomainPackaging Domain = "packaging" + DomainAerospace Domain = "aerospace" + DomainChemicals Domain = "chemicals" + DomainFoodBeverage Domain = "food_beverage" + DomainTextiles Domain = "textiles" + DomainPackaging Domain = "packaging" // Energie & Versorgung DomainUtilities Domain = "utilities" @@ -79,7 +73,7 @@ const ( DomainFacilityManagement Domain = "facility_management" // Gesundheit & Soziales - DomainHealthcare Domain = "healthcare" + DomainHealthcare Domain = "healthcare" DomainMedicalDevices Domain = "medical_devices" DomainPharma Domain = "pharma" DomainElderlyCare Domain = "elderly_care" @@ -98,10 +92,10 @@ const ( DomainInvestment Domain = "investment" // Handel & Logistik - DomainRetail Domain = "retail" - DomainEcommerce Domain = "ecommerce" - DomainWholesale Domain = "wholesale" - DomainLogistics Domain = "logistics" + DomainRetail Domain = "retail" + DomainEcommerce Domain = "ecommerce" + DomainWholesale Domain = "wholesale" + DomainLogistics Domain = "logistics" // IT & Telekommunikation DomainITServices Domain = "it_services" @@ -177,347 +171,6 @@ const ( TrainingNO TrainingAllowed = "NO" ) -// ============================================================================ -// Input Structs -// ============================================================================ - -// UseCaseIntake represents the user's input describing their planned AI use case -type UseCaseIntake struct { - // Free-text description of the use case - UseCaseText string `json:"use_case_text"` - - // Business domain - Domain Domain `json:"domain"` - - // Title for the assessment (optional) - Title string `json:"title,omitempty"` - - // Data types involved - DataTypes DataTypes `json:"data_types"` - - // Purpose of the processing - Purpose Purpose `json:"purpose"` - - // Level of automation - Automation AutomationLevel `json:"automation"` - - // Output characteristics - Outputs Outputs `json:"outputs"` - - // Hosting configuration - Hosting Hosting `json:"hosting"` - - // Model usage configuration - ModelUsage ModelUsage `json:"model_usage"` - - // Retention configuration - Retention Retention `json:"retention"` - - // Financial regulations context (DORA, MaRisk, BAIT) - // Only applicable for financial domains (banking, finance, insurance, investment) - FinancialContext *FinancialContext `json:"financial_context,omitempty"` - - // Opt-in to store raw text (otherwise only hash) - StoreRawText bool `json:"store_raw_text,omitempty"` -} - -// DataTypes specifies what kinds of data are processed -type DataTypes struct { - PersonalData bool `json:"personal_data"` - Article9Data bool `json:"article_9_data"` // Special categories (health, religion, etc.) - MinorData bool `json:"minor_data"` // Data of children - LicensePlates bool `json:"license_plates"` // KFZ-Kennzeichen - Images bool `json:"images"` // Photos/images of persons - Audio bool `json:"audio"` // Voice recordings - LocationData bool `json:"location_data"` // GPS/location tracking - BiometricData bool `json:"biometric_data"` // Fingerprints, face recognition - FinancialData bool `json:"financial_data"` // Bank accounts, salaries - EmployeeData bool `json:"employee_data"` // HR/employment data - CustomerData bool `json:"customer_data"` // Customer information - PublicData bool `json:"public_data"` // Publicly available data only -} - -// Purpose specifies the processing purpose -type Purpose struct { - CustomerSupport bool `json:"customer_support"` - Marketing bool `json:"marketing"` - Analytics bool `json:"analytics"` - Automation bool `json:"automation"` - EvaluationScoring bool `json:"evaluation_scoring"` // Scoring/ranking of persons - DecisionMaking bool `json:"decision_making"` // Automated decisions - Profiling bool `json:"profiling"` - Research bool `json:"research"` - InternalTools bool `json:"internal_tools"` - PublicService bool `json:"public_service"` -} - -// Outputs specifies output characteristics -type Outputs struct { - RecommendationsToUsers bool `json:"recommendations_to_users"` - RankingsOrScores bool `json:"rankings_or_scores"` // Outputs rankings/scores - LegalEffects bool `json:"legal_effects"` // Has legal consequences - AccessDecisions bool `json:"access_decisions"` // Grants/denies access - ContentGeneration bool `json:"content_generation"` // Generates text/media - DataExport bool `json:"data_export"` // Exports data externally -} - -// Hosting specifies where the AI runs -type Hosting struct { - Provider string `json:"provider,omitempty"` // e.g., "Azure", "AWS", "Hetzner", "On-Prem" - Region string `json:"region"` // "eu", "third_country", "on_prem" - DataResidency string `json:"data_residency,omitempty"` // Where data is stored -} - -// ModelUsage specifies how the model is used -type ModelUsage struct { - RAG bool `json:"rag"` // Retrieval-Augmented Generation only - Finetune bool `json:"finetune"` // Fine-tuning with data - Training bool `json:"training"` // Full training with data - Inference bool `json:"inference"` // Inference only -} - -// Retention specifies data retention -type Retention struct { - StorePrompts bool `json:"store_prompts"` - StoreResponses bool `json:"store_responses"` - RetentionDays int `json:"retention_days,omitempty"` - AnonymizeAfterUse bool `json:"anonymize_after_use"` -} - -// ============================================================================ -// Financial Regulations Structs (DORA, MaRisk, BAIT) -// ============================================================================ - -// FinancialEntityType represents the type of financial institution -type FinancialEntityType string - -const ( - FinancialEntityCreditInstitution FinancialEntityType = "CREDIT_INSTITUTION" - FinancialEntityPaymentServiceProvider FinancialEntityType = "PAYMENT_SERVICE_PROVIDER" - FinancialEntityEMoneyInstitution FinancialEntityType = "E_MONEY_INSTITUTION" - FinancialEntityInvestmentFirm FinancialEntityType = "INVESTMENT_FIRM" - FinancialEntityInsuranceCompany FinancialEntityType = "INSURANCE_COMPANY" - FinancialEntityCryptoAssetProvider FinancialEntityType = "CRYPTO_ASSET_PROVIDER" - FinancialEntityOther FinancialEntityType = "OTHER_FINANCIAL" -) - -// SizeCategory represents the significance category of a financial institution -type SizeCategory string - -const ( - SizeCategorySignificant SizeCategory = "SIGNIFICANT" - SizeCategoryLessSignificant SizeCategory = "LESS_SIGNIFICANT" - SizeCategorySmall SizeCategory = "SMALL" -) - -// ProviderLocation represents the location of an ICT service provider -type ProviderLocation string - -const ( - ProviderLocationEU ProviderLocation = "EU" - ProviderLocationEEA ProviderLocation = "EEA" - ProviderLocationAdequacyDecision ProviderLocation = "ADEQUACY_DECISION" - ProviderLocationThirdCountry ProviderLocation = "THIRD_COUNTRY" -) - -// FinancialEntity describes the financial institution context -type FinancialEntity struct { - Type FinancialEntityType `json:"type"` - Regulated bool `json:"regulated"` - SizeCategory SizeCategory `json:"size_category"` -} - -// ICTService describes ICT service characteristics for DORA compliance -type ICTService struct { - IsCritical bool `json:"is_critical"` - IsOutsourced bool `json:"is_outsourced"` - ProviderLocation ProviderLocation `json:"provider_location"` - ConcentrationRisk bool `json:"concentration_risk"` -} - -// FinancialAIApplication describes financial-specific AI application characteristics -type FinancialAIApplication struct { - AffectsCustomerDecisions bool `json:"affects_customer_decisions"` - AlgorithmicTrading bool `json:"algorithmic_trading"` - RiskAssessment bool `json:"risk_assessment"` - AMLKYC bool `json:"aml_kyc"` - ModelValidationDone bool `json:"model_validation_done"` -} - -// FinancialContext aggregates all financial regulation-specific information -type FinancialContext struct { - FinancialEntity FinancialEntity `json:"financial_entity"` - ICTService ICTService `json:"ict_service"` - AIApplication FinancialAIApplication `json:"ai_application"` -} - -// ============================================================================ -// Output Structs -// ============================================================================ - -// AssessmentResult represents the complete evaluation result -type AssessmentResult struct { - // Overall verdict - Feasibility Feasibility `json:"feasibility"` - RiskLevel RiskLevel `json:"risk_level"` - Complexity Complexity `json:"complexity"` - RiskScore int `json:"risk_score"` // 0-100 - - // Triggered rules - TriggeredRules []TriggeredRule `json:"triggered_rules"` - - // Required controls/mitigations - RequiredControls []RequiredControl `json:"required_controls"` - - // Recommended architecture patterns - RecommendedArchitecture []PatternRecommendation `json:"recommended_architecture"` - - // Patterns that must NOT be used - ForbiddenPatterns []ForbiddenPattern `json:"forbidden_patterns"` - - // Matching didactic examples - ExampleMatches []ExampleMatch `json:"example_matches"` - - // Special flags - DSFARecommended bool `json:"dsfa_recommended"` - Art22Risk bool `json:"art22_risk"` // Art. 22 GDPR automated decision risk - TrainingAllowed TrainingAllowed `json:"training_allowed"` - - // Summary for humans - Summary string `json:"summary"` - Recommendation string `json:"recommendation"` - AlternativeApproach string `json:"alternative_approach,omitempty"` -} - -// TriggeredRule represents a rule that was triggered during evaluation -type TriggeredRule struct { - Code string `json:"code"` // e.g., "R-001" - Category string `json:"category"` // e.g., "A. Datenklassifikation" - Title string `json:"title"` - Description string `json:"description"` - Severity Severity `json:"severity"` - ScoreDelta int `json:"score_delta"` - GDPRRef string `json:"gdpr_ref,omitempty"` // e.g., "Art. 9 DSGVO" - Rationale string `json:"rationale"` // Why this rule triggered -} - -// RequiredControl represents a control that must be implemented -type RequiredControl struct { - ID string `json:"id"` - Title string `json:"title"` - Description string `json:"description"` - Severity Severity `json:"severity"` - Category string `json:"category"` // "technical" or "organizational" - GDPRRef string `json:"gdpr_ref,omitempty"` -} - -// PatternRecommendation represents a recommended architecture pattern -type PatternRecommendation struct { - PatternID string `json:"pattern_id"` // e.g., "P-RAG-ONLY" - Title string `json:"title"` - Description string `json:"description"` - Rationale string `json:"rationale"` - Priority int `json:"priority"` // 1=highest -} - -// ForbiddenPattern represents a pattern that must NOT be used -type ForbiddenPattern struct { - PatternID string `json:"pattern_id"` - Title string `json:"title"` - Description string `json:"description"` - Reason string `json:"reason"` - GDPRRef string `json:"gdpr_ref,omitempty"` -} - -// ExampleMatch represents a matching didactic example -type ExampleMatch struct { - ExampleID string `json:"example_id"` - Title string `json:"title"` - Description string `json:"description"` - Similarity float64 `json:"similarity"` // 0.0 - 1.0 - Outcome string `json:"outcome"` // What happened / recommendation - Lessons string `json:"lessons"` // Key takeaways -} - -// ============================================================================ -// Database Entity -// ============================================================================ - -// Assessment represents a stored assessment in the database -type Assessment struct { - ID uuid.UUID `json:"id"` - TenantID uuid.UUID `json:"tenant_id"` - NamespaceID *uuid.UUID `json:"namespace_id,omitempty"` - Title string `json:"title"` - PolicyVersion string `json:"policy_version"` - Status string `json:"status"` // "completed", "draft" - - // Input - Intake UseCaseIntake `json:"intake"` - UseCaseTextStored bool `json:"use_case_text_stored"` - UseCaseTextHash string `json:"use_case_text_hash"` - - // Results - Feasibility Feasibility `json:"feasibility"` - RiskLevel RiskLevel `json:"risk_level"` - Complexity Complexity `json:"complexity"` - RiskScore int `json:"risk_score"` - TriggeredRules []TriggeredRule `json:"triggered_rules"` - RequiredControls []RequiredControl `json:"required_controls"` - RecommendedArchitecture []PatternRecommendation `json:"recommended_architecture"` - ForbiddenPatterns []ForbiddenPattern `json:"forbidden_patterns"` - ExampleMatches []ExampleMatch `json:"example_matches"` - DSFARecommended bool `json:"dsfa_recommended"` - Art22Risk bool `json:"art22_risk"` - TrainingAllowed TrainingAllowed `json:"training_allowed"` - - // Corpus Versioning (RAG) - CorpusVersionID *uuid.UUID `json:"corpus_version_id,omitempty"` - CorpusVersion string `json:"corpus_version,omitempty"` - - // LLM Explanation (optional) - ExplanationText *string `json:"explanation_text,omitempty"` - ExplanationGeneratedAt *time.Time `json:"explanation_generated_at,omitempty"` - ExplanationModel *string `json:"explanation_model,omitempty"` - - // Domain - Domain Domain `json:"domain"` - - // Audit - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - CreatedBy uuid.UUID `json:"created_by"` -} - -// ============================================================================ -// API Request/Response Types -// ============================================================================ - -// AssessRequest is the API request for creating an assessment -type AssessRequest struct { - Intake UseCaseIntake `json:"intake"` -} - -// AssessResponse is the API response for an assessment -type AssessResponse struct { - Assessment Assessment `json:"assessment"` - Result AssessmentResult `json:"result"` - Escalation *Escalation `json:"escalation,omitempty"` -} - -// ExplainRequest is the API request for generating an explanation -type ExplainRequest struct { - Language string `json:"language,omitempty"` // "de" or "en", default "de" -} - -// ExplainResponse is the API response for an explanation -type ExplainResponse struct { - ExplanationText string `json:"explanation_text"` - GeneratedAt time.Time `json:"generated_at"` - Model string `json:"model"` - LegalContext *LegalContext `json:"legal_context,omitempty"` -} - // ExportFormat specifies the export format type ExportFormat string diff --git a/ai-compliance-sdk/internal/ucca/models_assessment.go b/ai-compliance-sdk/internal/ucca/models_assessment.go new file mode 100644 index 0000000..8716f7a --- /dev/null +++ b/ai-compliance-sdk/internal/ucca/models_assessment.go @@ -0,0 +1,174 @@ +package ucca + +import ( + "time" + + "github.com/google/uuid" +) + +// ============================================================================ +// Output Structs +// ============================================================================ + +// AssessmentResult represents the complete evaluation result +type AssessmentResult struct { + // Overall verdict + Feasibility Feasibility `json:"feasibility"` + RiskLevel RiskLevel `json:"risk_level"` + Complexity Complexity `json:"complexity"` + RiskScore int `json:"risk_score"` // 0-100 + + // Triggered rules + TriggeredRules []TriggeredRule `json:"triggered_rules"` + + // Required controls/mitigations + RequiredControls []RequiredControl `json:"required_controls"` + + // Recommended architecture patterns + RecommendedArchitecture []PatternRecommendation `json:"recommended_architecture"` + + // Patterns that must NOT be used + ForbiddenPatterns []ForbiddenPattern `json:"forbidden_patterns"` + + // Matching didactic examples + ExampleMatches []ExampleMatch `json:"example_matches"` + + // Special flags + DSFARecommended bool `json:"dsfa_recommended"` + Art22Risk bool `json:"art22_risk"` // Art. 22 GDPR automated decision risk + TrainingAllowed TrainingAllowed `json:"training_allowed"` + + // Summary for humans + Summary string `json:"summary"` + Recommendation string `json:"recommendation"` + AlternativeApproach string `json:"alternative_approach,omitempty"` +} + +// TriggeredRule represents a rule that was triggered during evaluation +type TriggeredRule struct { + Code string `json:"code"` // e.g., "R-001" + Category string `json:"category"` // e.g., "A. Datenklassifikation" + Title string `json:"title"` + Description string `json:"description"` + Severity Severity `json:"severity"` + ScoreDelta int `json:"score_delta"` + GDPRRef string `json:"gdpr_ref,omitempty"` // e.g., "Art. 9 DSGVO" + Rationale string `json:"rationale"` // Why this rule triggered +} + +// RequiredControl represents a control that must be implemented +type RequiredControl struct { + ID string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + Severity Severity `json:"severity"` + Category string `json:"category"` // "technical" or "organizational" + GDPRRef string `json:"gdpr_ref,omitempty"` +} + +// PatternRecommendation represents a recommended architecture pattern +type PatternRecommendation struct { + PatternID string `json:"pattern_id"` // e.g., "P-RAG-ONLY" + Title string `json:"title"` + Description string `json:"description"` + Rationale string `json:"rationale"` + Priority int `json:"priority"` // 1=highest +} + +// ForbiddenPattern represents a pattern that must NOT be used +type ForbiddenPattern struct { + PatternID string `json:"pattern_id"` + Title string `json:"title"` + Description string `json:"description"` + Reason string `json:"reason"` + GDPRRef string `json:"gdpr_ref,omitempty"` +} + +// ExampleMatch represents a matching didactic example +type ExampleMatch struct { + ExampleID string `json:"example_id"` + Title string `json:"title"` + Description string `json:"description"` + Similarity float64 `json:"similarity"` // 0.0 - 1.0 + Outcome string `json:"outcome"` // What happened / recommendation + Lessons string `json:"lessons"` // Key takeaways +} + +// ============================================================================ +// Database Entity +// ============================================================================ + +// Assessment represents a stored assessment in the database +type Assessment struct { + ID uuid.UUID `json:"id"` + TenantID uuid.UUID `json:"tenant_id"` + NamespaceID *uuid.UUID `json:"namespace_id,omitempty"` + Title string `json:"title"` + PolicyVersion string `json:"policy_version"` + Status string `json:"status"` // "completed", "draft" + + // Input + Intake UseCaseIntake `json:"intake"` + UseCaseTextStored bool `json:"use_case_text_stored"` + UseCaseTextHash string `json:"use_case_text_hash"` + + // Results + Feasibility Feasibility `json:"feasibility"` + RiskLevel RiskLevel `json:"risk_level"` + Complexity Complexity `json:"complexity"` + RiskScore int `json:"risk_score"` + TriggeredRules []TriggeredRule `json:"triggered_rules"` + RequiredControls []RequiredControl `json:"required_controls"` + RecommendedArchitecture []PatternRecommendation `json:"recommended_architecture"` + ForbiddenPatterns []ForbiddenPattern `json:"forbidden_patterns"` + ExampleMatches []ExampleMatch `json:"example_matches"` + DSFARecommended bool `json:"dsfa_recommended"` + Art22Risk bool `json:"art22_risk"` + TrainingAllowed TrainingAllowed `json:"training_allowed"` + + // Corpus Versioning (RAG) + CorpusVersionID *uuid.UUID `json:"corpus_version_id,omitempty"` + CorpusVersion string `json:"corpus_version,omitempty"` + + // LLM Explanation (optional) + ExplanationText *string `json:"explanation_text,omitempty"` + ExplanationGeneratedAt *time.Time `json:"explanation_generated_at,omitempty"` + ExplanationModel *string `json:"explanation_model,omitempty"` + + // Domain + Domain Domain `json:"domain"` + + // Audit + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + CreatedBy uuid.UUID `json:"created_by"` +} + +// ============================================================================ +// API Request/Response Types +// ============================================================================ + +// AssessRequest is the API request for creating an assessment +type AssessRequest struct { + Intake UseCaseIntake `json:"intake"` +} + +// AssessResponse is the API response for an assessment +type AssessResponse struct { + Assessment Assessment `json:"assessment"` + Result AssessmentResult `json:"result"` + Escalation *Escalation `json:"escalation,omitempty"` +} + +// ExplainRequest is the API request for generating an explanation +type ExplainRequest struct { + Language string `json:"language,omitempty"` // "de" or "en", default "de" +} + +// ExplainResponse is the API response for an explanation +type ExplainResponse struct { + ExplanationText string `json:"explanation_text"` + GeneratedAt time.Time `json:"generated_at"` + Model string `json:"model"` + LegalContext *LegalContext `json:"legal_context,omitempty"` +} diff --git a/ai-compliance-sdk/internal/ucca/models_intake.go b/ai-compliance-sdk/internal/ucca/models_intake.go new file mode 100644 index 0000000..f36e176 --- /dev/null +++ b/ai-compliance-sdk/internal/ucca/models_intake.go @@ -0,0 +1,175 @@ +package ucca + +// ============================================================================ +// Input Structs +// ============================================================================ + +// UseCaseIntake represents the user's input describing their planned AI use case +type UseCaseIntake struct { + // Free-text description of the use case + UseCaseText string `json:"use_case_text"` + + // Business domain + Domain Domain `json:"domain"` + + // Title for the assessment (optional) + Title string `json:"title,omitempty"` + + // Data types involved + DataTypes DataTypes `json:"data_types"` + + // Purpose of the processing + Purpose Purpose `json:"purpose"` + + // Level of automation + Automation AutomationLevel `json:"automation"` + + // Output characteristics + Outputs Outputs `json:"outputs"` + + // Hosting configuration + Hosting Hosting `json:"hosting"` + + // Model usage configuration + ModelUsage ModelUsage `json:"model_usage"` + + // Retention configuration + Retention Retention `json:"retention"` + + // Financial regulations context (DORA, MaRisk, BAIT) + // Only applicable for financial domains (banking, finance, insurance, investment) + FinancialContext *FinancialContext `json:"financial_context,omitempty"` + + // Opt-in to store raw text (otherwise only hash) + StoreRawText bool `json:"store_raw_text,omitempty"` +} + +// DataTypes specifies what kinds of data are processed +type DataTypes struct { + PersonalData bool `json:"personal_data"` + Article9Data bool `json:"article_9_data"` // Special categories (health, religion, etc.) + MinorData bool `json:"minor_data"` // Data of children + LicensePlates bool `json:"license_plates"` // KFZ-Kennzeichen + Images bool `json:"images"` // Photos/images of persons + Audio bool `json:"audio"` // Voice recordings + LocationData bool `json:"location_data"` // GPS/location tracking + BiometricData bool `json:"biometric_data"` // Fingerprints, face recognition + FinancialData bool `json:"financial_data"` // Bank accounts, salaries + EmployeeData bool `json:"employee_data"` // HR/employment data + CustomerData bool `json:"customer_data"` // Customer information + PublicData bool `json:"public_data"` // Publicly available data only +} + +// Purpose specifies the processing purpose +type Purpose struct { + CustomerSupport bool `json:"customer_support"` + Marketing bool `json:"marketing"` + Analytics bool `json:"analytics"` + Automation bool `json:"automation"` + EvaluationScoring bool `json:"evaluation_scoring"` // Scoring/ranking of persons + DecisionMaking bool `json:"decision_making"` // Automated decisions + Profiling bool `json:"profiling"` + Research bool `json:"research"` + InternalTools bool `json:"internal_tools"` + PublicService bool `json:"public_service"` +} + +// Outputs specifies output characteristics +type Outputs struct { + RecommendationsToUsers bool `json:"recommendations_to_users"` + RankingsOrScores bool `json:"rankings_or_scores"` // Outputs rankings/scores + LegalEffects bool `json:"legal_effects"` // Has legal consequences + AccessDecisions bool `json:"access_decisions"` // Grants/denies access + ContentGeneration bool `json:"content_generation"` // Generates text/media + DataExport bool `json:"data_export"` // Exports data externally +} + +// Hosting specifies where the AI runs +type Hosting struct { + Provider string `json:"provider,omitempty"` // e.g., "Azure", "AWS", "Hetzner", "On-Prem" + Region string `json:"region"` // "eu", "third_country", "on_prem" + DataResidency string `json:"data_residency,omitempty"` // Where data is stored +} + +// ModelUsage specifies how the model is used +type ModelUsage struct { + RAG bool `json:"rag"` // Retrieval-Augmented Generation only + Finetune bool `json:"finetune"` // Fine-tuning with data + Training bool `json:"training"` // Full training with data + Inference bool `json:"inference"` // Inference only +} + +// Retention specifies data retention +type Retention struct { + StorePrompts bool `json:"store_prompts"` + StoreResponses bool `json:"store_responses"` + RetentionDays int `json:"retention_days,omitempty"` + AnonymizeAfterUse bool `json:"anonymize_after_use"` +} + +// ============================================================================ +// Financial Regulations Structs (DORA, MaRisk, BAIT) +// ============================================================================ + +// FinancialEntityType represents the type of financial institution +type FinancialEntityType string + +const ( + FinancialEntityCreditInstitution FinancialEntityType = "CREDIT_INSTITUTION" + FinancialEntityPaymentServiceProvider FinancialEntityType = "PAYMENT_SERVICE_PROVIDER" + FinancialEntityEMoneyInstitution FinancialEntityType = "E_MONEY_INSTITUTION" + FinancialEntityInvestmentFirm FinancialEntityType = "INVESTMENT_FIRM" + FinancialEntityInsuranceCompany FinancialEntityType = "INSURANCE_COMPANY" + FinancialEntityCryptoAssetProvider FinancialEntityType = "CRYPTO_ASSET_PROVIDER" + FinancialEntityOther FinancialEntityType = "OTHER_FINANCIAL" +) + +// SizeCategory represents the significance category of a financial institution +type SizeCategory string + +const ( + SizeCategorySignificant SizeCategory = "SIGNIFICANT" + SizeCategoryLessSignificant SizeCategory = "LESS_SIGNIFICANT" + SizeCategorySmall SizeCategory = "SMALL" +) + +// ProviderLocation represents the location of an ICT service provider +type ProviderLocation string + +const ( + ProviderLocationEU ProviderLocation = "EU" + ProviderLocationEEA ProviderLocation = "EEA" + ProviderLocationAdequacyDecision ProviderLocation = "ADEQUACY_DECISION" + ProviderLocationThirdCountry ProviderLocation = "THIRD_COUNTRY" +) + +// FinancialEntity describes the financial institution context +type FinancialEntity struct { + Type FinancialEntityType `json:"type"` + Regulated bool `json:"regulated"` + SizeCategory SizeCategory `json:"size_category"` +} + +// ICTService describes ICT service characteristics for DORA compliance +type ICTService struct { + IsCritical bool `json:"is_critical"` + IsOutsourced bool `json:"is_outsourced"` + ProviderLocation ProviderLocation `json:"provider_location"` + ConcentrationRisk bool `json:"concentration_risk"` +} + +// FinancialAIApplication describes financial-specific AI application characteristics +type FinancialAIApplication struct { + AffectsCustomerDecisions bool `json:"affects_customer_decisions"` + AlgorithmicTrading bool `json:"algorithmic_trading"` + RiskAssessment bool `json:"risk_assessment"` + AMLKYC bool `json:"aml_kyc"` + ModelValidationDone bool `json:"model_validation_done"` +} + +// FinancialContext aggregates all financial regulation-specific information +type FinancialContext struct { + FinancialEntity FinancialEntity `json:"financial_entity"` + ICTService ICTService `json:"ict_service"` + AIApplication FinancialAIApplication `json:"ai_application"` +} diff --git a/ai-compliance-sdk/internal/ucca/obligations_registry.go b/ai-compliance-sdk/internal/ucca/obligations_registry.go index 6a769b8..bc4d33b 100644 --- a/ai-compliance-sdk/internal/ucca/obligations_registry.go +++ b/ai-compliance-sdk/internal/ucca/obligations_registry.go @@ -428,74 +428,3 @@ func (r *ObligationsRegistry) generateExecutiveSummary(overview *ManagementOblig return summary } -// containsString checks if a slice contains a string -func containsString(slice []string, s string) bool { - for _, item := range slice { - if item == s { - return true - } - } - return false -} - -// ============================================================================ -// Grouping Methods -// ============================================================================ - -// GroupByRegulation groups obligations by their regulation ID -func (r *ObligationsRegistry) GroupByRegulation(obligations []Obligation) map[string][]Obligation { - result := make(map[string][]Obligation) - for _, obl := range obligations { - result[obl.RegulationID] = append(result[obl.RegulationID], obl) - } - return result -} - -// GroupByDeadline groups obligations by deadline timeframe -func (r *ObligationsRegistry) GroupByDeadline(obligations []Obligation) ObligationsByDeadlineResponse { - result := ObligationsByDeadlineResponse{ - Overdue: []Obligation{}, - ThisWeek: []Obligation{}, - ThisMonth: []Obligation{}, - NextQuarter: []Obligation{}, - Later: []Obligation{}, - NoDeadline: []Obligation{}, - } - - now := time.Now() - oneWeek := now.AddDate(0, 0, 7) - oneMonth := now.AddDate(0, 1, 0) - threeMonths := now.AddDate(0, 3, 0) - - for _, obl := range obligations { - if obl.Deadline == nil || obl.Deadline.Type != DeadlineAbsolute || obl.Deadline.Date == nil { - result.NoDeadline = append(result.NoDeadline, obl) - continue - } - - deadline := *obl.Deadline.Date - switch { - case deadline.Before(now): - result.Overdue = append(result.Overdue, obl) - case deadline.Before(oneWeek): - result.ThisWeek = append(result.ThisWeek, obl) - case deadline.Before(oneMonth): - result.ThisMonth = append(result.ThisMonth, obl) - case deadline.Before(threeMonths): - result.NextQuarter = append(result.NextQuarter, obl) - default: - result.Later = append(result.Later, obl) - } - } - - return result -} - -// GroupByResponsible groups obligations by responsible role -func (r *ObligationsRegistry) GroupByResponsible(obligations []Obligation) map[ResponsibleRole][]Obligation { - result := make(map[ResponsibleRole][]Obligation) - for _, obl := range obligations { - result[obl.Responsible] = append(result[obl.Responsible], obl) - } - return result -} diff --git a/ai-compliance-sdk/internal/ucca/obligations_registry_grouping.go b/ai-compliance-sdk/internal/ucca/obligations_registry_grouping.go new file mode 100644 index 0000000..b679d38 --- /dev/null +++ b/ai-compliance-sdk/internal/ucca/obligations_registry_grouping.go @@ -0,0 +1,75 @@ +package ucca + +import "time" + +// ============================================================================ +// Grouping Methods +// ============================================================================ + +// GroupByRegulation groups obligations by their regulation ID +func (r *ObligationsRegistry) GroupByRegulation(obligations []Obligation) map[string][]Obligation { + result := make(map[string][]Obligation) + for _, obl := range obligations { + result[obl.RegulationID] = append(result[obl.RegulationID], obl) + } + return result +} + +// GroupByDeadline groups obligations by deadline timeframe +func (r *ObligationsRegistry) GroupByDeadline(obligations []Obligation) ObligationsByDeadlineResponse { + result := ObligationsByDeadlineResponse{ + Overdue: []Obligation{}, + ThisWeek: []Obligation{}, + ThisMonth: []Obligation{}, + NextQuarter: []Obligation{}, + Later: []Obligation{}, + NoDeadline: []Obligation{}, + } + + now := time.Now() + oneWeek := now.AddDate(0, 0, 7) + oneMonth := now.AddDate(0, 1, 0) + threeMonths := now.AddDate(0, 3, 0) + + for _, obl := range obligations { + if obl.Deadline == nil || obl.Deadline.Type != DeadlineAbsolute || obl.Deadline.Date == nil { + result.NoDeadline = append(result.NoDeadline, obl) + continue + } + + deadline := *obl.Deadline.Date + switch { + case deadline.Before(now): + result.Overdue = append(result.Overdue, obl) + case deadline.Before(oneWeek): + result.ThisWeek = append(result.ThisWeek, obl) + case deadline.Before(oneMonth): + result.ThisMonth = append(result.ThisMonth, obl) + case deadline.Before(threeMonths): + result.NextQuarter = append(result.NextQuarter, obl) + default: + result.Later = append(result.Later, obl) + } + } + + return result +} + +// GroupByResponsible groups obligations by responsible role +func (r *ObligationsRegistry) GroupByResponsible(obligations []Obligation) map[ResponsibleRole][]Obligation { + result := make(map[ResponsibleRole][]Obligation) + for _, obl := range obligations { + result[obl.Responsible] = append(result[obl.Responsible], obl) + } + return result +} + +// containsString checks if a slice contains a string +func containsString(slice []string, s string) bool { + for _, item := range slice { + if item == s { + return true + } + } + return false +} diff --git a/ai-compliance-sdk/internal/ucca/pdf_export.go b/ai-compliance-sdk/internal/ucca/pdf_export.go index 40f6887..baa8e8b 100644 --- a/ai-compliance-sdk/internal/ucca/pdf_export.go +++ b/ai-compliance-sdk/internal/ucca/pdf_export.go @@ -408,103 +408,3 @@ func truncateString(s string, maxLen int) string { } return s[:maxLen-3] + "..." } - -// ExportMarkdown exports the overview as Markdown (for compatibility) -func (e *PDFExporter) ExportMarkdown(overview *ManagementObligationsOverview) (*ExportMemoResponse, error) { - var buf bytes.Buffer - - // Title - buf.WriteString(fmt.Sprintf("# Regulatorische Pflichten-Uebersicht\n\n")) - if overview.OrganizationName != "" { - buf.WriteString(fmt.Sprintf("**Organisation:** %s\n\n", overview.OrganizationName)) - } - buf.WriteString(fmt.Sprintf("**Stand:** %s\n\n", overview.AssessmentDate.Format("02.01.2006"))) - - // Executive Summary - buf.WriteString("## Executive Summary\n\n") - summary := overview.ExecutiveSummary - buf.WriteString(fmt.Sprintf("| Metrik | Wert |\n")) - buf.WriteString(fmt.Sprintf("|--------|------|\n")) - buf.WriteString(fmt.Sprintf("| Anwendbare Regulierungen | %d |\n", summary.TotalRegulations)) - buf.WriteString(fmt.Sprintf("| Gesamtzahl Pflichten | %d |\n", summary.TotalObligations)) - buf.WriteString(fmt.Sprintf("| Kritische Pflichten | %d |\n", summary.CriticalObligations)) - buf.WriteString(fmt.Sprintf("| Kommende Fristen (30 Tage) | %d |\n", summary.UpcomingDeadlines)) - buf.WriteString(fmt.Sprintf("| Compliance Score | %d%% |\n\n", summary.ComplianceScore)) - - // Key Risks - if len(summary.KeyRisks) > 0 { - buf.WriteString("### Wesentliche Risiken\n\n") - for _, risk := range summary.KeyRisks { - buf.WriteString(fmt.Sprintf("- %s\n", risk)) - } - buf.WriteString("\n") - } - - // Recommended Actions - if len(summary.RecommendedActions) > 0 { - buf.WriteString("### Empfohlene Massnahmen\n\n") - for _, action := range summary.RecommendedActions { - buf.WriteString(fmt.Sprintf("- %s\n", action)) - } - buf.WriteString("\n") - } - - // Applicable Regulations - buf.WriteString("## Anwendbare Regulierungen\n\n") - buf.WriteString("| Regulierung | Klassifizierung | Pflichten | Grund |\n") - buf.WriteString("|-------------|-----------------|-----------|-------|\n") - for _, reg := range overview.ApplicableRegulations { - buf.WriteString(fmt.Sprintf("| %s | %s | %d | %s |\n", reg.Name, reg.Classification, reg.ObligationCount, reg.Reason)) - } - buf.WriteString("\n") - - // Sanctions Summary - buf.WriteString("## Sanktionsrisiken\n\n") - sanctions := overview.SanctionsSummary - if sanctions.MaxFinancialRisk != "" { - buf.WriteString(fmt.Sprintf("- **Max. Finanzrisiko:** %s\n", sanctions.MaxFinancialRisk)) - } - buf.WriteString(fmt.Sprintf("- **Persoenliche Haftung:** %v\n", sanctions.PersonalLiabilityRisk)) - buf.WriteString(fmt.Sprintf("- **Strafrechtliche Konsequenzen:** %v\n\n", sanctions.CriminalLiabilityRisk)) - if sanctions.Summary != "" { - buf.WriteString(fmt.Sprintf("*%s*\n\n", sanctions.Summary)) - } - - // Obligations - buf.WriteString("## Pflichten-Uebersicht\n\n") - for _, obl := range overview.Obligations { - buf.WriteString(fmt.Sprintf("### %s - %s\n\n", obl.ID, obl.Title)) - buf.WriteString(fmt.Sprintf("**Prioritaet:** %s | **Verantwortlich:** %s\n\n", obl.Priority, obl.Responsible)) - if len(obl.LegalBasis) > 0 { - buf.WriteString("**Rechtsgrundlage:** ") - for i, lb := range obl.LegalBasis { - if i > 0 { - buf.WriteString(", ") - } - buf.WriteString(lb.Norm) - } - buf.WriteString("\n\n") - } - buf.WriteString(fmt.Sprintf("%s\n\n", obl.Description)) - } - - // Incident Deadlines - if len(overview.IncidentDeadlines) > 0 { - buf.WriteString("## Meldepflichten bei Vorfaellen\n\n") - for _, dl := range overview.IncidentDeadlines { - buf.WriteString(fmt.Sprintf("- **%s:** %s an %s\n", dl.Phase, dl.Deadline, dl.Recipient)) - } - buf.WriteString("\n") - } - - // Footer - buf.WriteString("---\n\n") - buf.WriteString(fmt.Sprintf("*Generiert am %s mit BreakPilot AI Compliance SDK*\n", time.Now().Format("02.01.2006 15:04"))) - - return &ExportMemoResponse{ - Content: buf.String(), - ContentType: "text/markdown", - Filename: fmt.Sprintf("pflichten-uebersicht-%s.md", time.Now().Format("2006-01-02")), - GeneratedAt: time.Now(), - }, nil -} diff --git a/ai-compliance-sdk/internal/ucca/pdf_export_markdown.go b/ai-compliance-sdk/internal/ucca/pdf_export_markdown.go new file mode 100644 index 0000000..27c6d0a --- /dev/null +++ b/ai-compliance-sdk/internal/ucca/pdf_export_markdown.go @@ -0,0 +1,107 @@ +package ucca + +import ( + "bytes" + "fmt" + "time" +) + +// ExportMarkdown exports the overview as Markdown (for compatibility) +func (e *PDFExporter) ExportMarkdown(overview *ManagementObligationsOverview) (*ExportMemoResponse, error) { + var buf bytes.Buffer + + // Title + buf.WriteString(fmt.Sprintf("# Regulatorische Pflichten-Uebersicht\n\n")) + if overview.OrganizationName != "" { + buf.WriteString(fmt.Sprintf("**Organisation:** %s\n\n", overview.OrganizationName)) + } + buf.WriteString(fmt.Sprintf("**Stand:** %s\n\n", overview.AssessmentDate.Format("02.01.2006"))) + + // Executive Summary + buf.WriteString("## Executive Summary\n\n") + summary := overview.ExecutiveSummary + buf.WriteString(fmt.Sprintf("| Metrik | Wert |\n")) + buf.WriteString(fmt.Sprintf("|--------|------|\n")) + buf.WriteString(fmt.Sprintf("| Anwendbare Regulierungen | %d |\n", summary.TotalRegulations)) + buf.WriteString(fmt.Sprintf("| Gesamtzahl Pflichten | %d |\n", summary.TotalObligations)) + buf.WriteString(fmt.Sprintf("| Kritische Pflichten | %d |\n", summary.CriticalObligations)) + buf.WriteString(fmt.Sprintf("| Kommende Fristen (30 Tage) | %d |\n", summary.UpcomingDeadlines)) + buf.WriteString(fmt.Sprintf("| Compliance Score | %d%% |\n\n", summary.ComplianceScore)) + + // Key Risks + if len(summary.KeyRisks) > 0 { + buf.WriteString("### Wesentliche Risiken\n\n") + for _, risk := range summary.KeyRisks { + buf.WriteString(fmt.Sprintf("- %s\n", risk)) + } + buf.WriteString("\n") + } + + // Recommended Actions + if len(summary.RecommendedActions) > 0 { + buf.WriteString("### Empfohlene Massnahmen\n\n") + for _, action := range summary.RecommendedActions { + buf.WriteString(fmt.Sprintf("- %s\n", action)) + } + buf.WriteString("\n") + } + + // Applicable Regulations + buf.WriteString("## Anwendbare Regulierungen\n\n") + buf.WriteString("| Regulierung | Klassifizierung | Pflichten | Grund |\n") + buf.WriteString("|-------------|-----------------|-----------|-------|\n") + for _, reg := range overview.ApplicableRegulations { + buf.WriteString(fmt.Sprintf("| %s | %s | %d | %s |\n", reg.Name, reg.Classification, reg.ObligationCount, reg.Reason)) + } + buf.WriteString("\n") + + // Sanctions Summary + buf.WriteString("## Sanktionsrisiken\n\n") + sanctions := overview.SanctionsSummary + if sanctions.MaxFinancialRisk != "" { + buf.WriteString(fmt.Sprintf("- **Max. Finanzrisiko:** %s\n", sanctions.MaxFinancialRisk)) + } + buf.WriteString(fmt.Sprintf("- **Persoenliche Haftung:** %v\n", sanctions.PersonalLiabilityRisk)) + buf.WriteString(fmt.Sprintf("- **Strafrechtliche Konsequenzen:** %v\n\n", sanctions.CriminalLiabilityRisk)) + if sanctions.Summary != "" { + buf.WriteString(fmt.Sprintf("*%s*\n\n", sanctions.Summary)) + } + + // Obligations + buf.WriteString("## Pflichten-Uebersicht\n\n") + for _, obl := range overview.Obligations { + buf.WriteString(fmt.Sprintf("### %s - %s\n\n", obl.ID, obl.Title)) + buf.WriteString(fmt.Sprintf("**Prioritaet:** %s | **Verantwortlich:** %s\n\n", obl.Priority, obl.Responsible)) + if len(obl.LegalBasis) > 0 { + buf.WriteString("**Rechtsgrundlage:** ") + for i, lb := range obl.LegalBasis { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(lb.Norm) + } + buf.WriteString("\n\n") + } + buf.WriteString(fmt.Sprintf("%s\n\n", obl.Description)) + } + + // Incident Deadlines + if len(overview.IncidentDeadlines) > 0 { + buf.WriteString("## Meldepflichten bei Vorfaellen\n\n") + for _, dl := range overview.IncidentDeadlines { + buf.WriteString(fmt.Sprintf("- **%s:** %s an %s\n", dl.Phase, dl.Deadline, dl.Recipient)) + } + buf.WriteString("\n") + } + + // Footer + buf.WriteString("---\n\n") + buf.WriteString(fmt.Sprintf("*Generiert am %s mit BreakPilot AI Compliance SDK*\n", time.Now().Format("02.01.2006 15:04"))) + + return &ExportMemoResponse{ + Content: buf.String(), + ContentType: "text/markdown", + Filename: fmt.Sprintf("pflichten-uebersicht-%s.md", time.Now().Format("2006-01-02")), + GeneratedAt: time.Now(), + }, nil +}