refactor(go/ucca): split license_policy, models, pdf_export, escalation_store, obligations_registry
Split 5 oversized files (501-583 LOC each) into focused units all under 500 LOC: - license_policy.go → +_types.go (engine logic / type definitions) - models.go → +_intake.go, +_assessment.go (enums+domains / intake structs / output+DB types) - pdf_export.go → +_markdown.go (PDF export / markdown export) - escalation_store.go → +_dsb.go (main escalation ops / DSB pool ops) - obligations_registry.go → +_grouping.go (registry core / grouping methods) All files remain in package ucca. Zero behavior changes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -387,116 +387,3 @@ func (s *EscalationStore) GetEscalationStats(ctx context.Context, tenantID uuid.
|
|||||||
return stats, nil
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
123
ai-compliance-sdk/internal/ucca/escalation_store_dsb.go
Normal file
123
ai-compliance-sdk/internal/ucca/escalation_store_dsb.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -11,66 +11,6 @@ import (
|
|||||||
// Handles license/copyright compliance for standards and norms
|
// 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
|
// LicensePolicyEngine evaluates license compliance
|
||||||
type LicensePolicyEngine struct {
|
type LicensePolicyEngine struct {
|
||||||
// Configuration can be added here
|
// 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.Reason = "Link-only Modus ist ohne spezielle Lizenz erlaubt"
|
||||||
result.RiskScore = 0
|
result.RiskScore = 0
|
||||||
|
|
||||||
// Very restrictive output
|
|
||||||
result.OutputRestrictions = &OutputRestrictions{
|
result.OutputRestrictions = &OutputRestrictions{
|
||||||
AllowQuotes: false,
|
AllowQuotes: false,
|
||||||
MaxQuoteLength: 0,
|
MaxQuoteLength: 0,
|
||||||
@@ -153,7 +92,6 @@ func (e *LicensePolicyEngine) evaluateLinkOnlyMode(facts *LicensedContentFacts,
|
|||||||
AllowExport: false,
|
AllowExport: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recommend control for proper setup
|
|
||||||
result.RequiredControls = append(result.RequiredControls, LicenseControl{
|
result.RequiredControls = append(result.RequiredControls, LicenseControl{
|
||||||
ID: "CTRL-LINK-ONLY-MODE",
|
ID: "CTRL-LINK-ONLY-MODE",
|
||||||
Title: "Link-only / Evidence Navigator aktivieren",
|
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.Reason = "Notes-only Modus mit kundeneigenen Zusammenfassungen"
|
||||||
result.RiskScore = 10
|
result.RiskScore = 10
|
||||||
|
|
||||||
// Allow paraphrased content
|
|
||||||
result.OutputRestrictions = &OutputRestrictions{
|
result.OutputRestrictions = &OutputRestrictions{
|
||||||
AllowQuotes: false, // No direct quotes from norms
|
AllowQuotes: false, // No direct quotes from norms
|
||||||
MaxQuoteLength: 0,
|
MaxQuoteLength: 0,
|
||||||
@@ -244,7 +181,6 @@ func (e *LicensePolicyEngine) evaluateExcerptOnlyMode(facts *LicensedContentFact
|
|||||||
func (e *LicensePolicyEngine) evaluateFulltextRAGMode(facts *LicensedContentFacts, result *LicensePolicyResult) {
|
func (e *LicensePolicyEngine) evaluateFulltextRAGMode(facts *LicensedContentFacts, result *LicensePolicyResult) {
|
||||||
result.RiskScore = 60
|
result.RiskScore = 60
|
||||||
|
|
||||||
// Check if AI use is explicitly permitted AND proof is uploaded
|
|
||||||
if facts.AIUsePermitted == "YES" && facts.ProofUploaded {
|
if facts.AIUsePermitted == "YES" && facts.ProofUploaded {
|
||||||
result.EffectiveMode = "FULLTEXT_RAG"
|
result.EffectiveMode = "FULLTEXT_RAG"
|
||||||
result.Allowed = true
|
result.Allowed = true
|
||||||
@@ -290,7 +226,6 @@ func (e *LicensePolicyEngine) evaluateFulltextRAGMode(facts *LicensedContentFact
|
|||||||
|
|
||||||
result.EscalationLevel = "E3"
|
result.EscalationLevel = "E3"
|
||||||
|
|
||||||
// Set stop line
|
|
||||||
result.StopLine = &LicenseStopLine{
|
result.StopLine = &LicenseStopLine{
|
||||||
ID: "STOP_FULLTEXT_WITHOUT_PROOF",
|
ID: "STOP_FULLTEXT_WITHOUT_PROOF",
|
||||||
Title: "Volltext-RAG blockiert",
|
Title: "Volltext-RAG blockiert",
|
||||||
@@ -312,7 +247,6 @@ func (e *LicensePolicyEngine) evaluateFulltextRAGMode(facts *LicensedContentFact
|
|||||||
func (e *LicensePolicyEngine) evaluateTrainingMode(facts *LicensedContentFacts, result *LicensePolicyResult) {
|
func (e *LicensePolicyEngine) evaluateTrainingMode(facts *LicensedContentFacts, result *LicensePolicyResult) {
|
||||||
result.RiskScore = 80
|
result.RiskScore = 80
|
||||||
|
|
||||||
// Training is almost always blocked for standards
|
|
||||||
if facts.AIUsePermitted == "YES" && facts.ProofUploaded && facts.LicenseType == "AI_LICENSE" {
|
if facts.AIUsePermitted == "YES" && facts.ProofUploaded && facts.LicenseType == "AI_LICENSE" {
|
||||||
result.EffectiveMode = "TRAINING"
|
result.EffectiveMode = "TRAINING"
|
||||||
result.Allowed = true
|
result.Allowed = true
|
||||||
@@ -353,10 +287,8 @@ func (e *LicensePolicyEngine) evaluateTrainingMode(facts *LicensedContentFacts,
|
|||||||
|
|
||||||
// applyPublisherRestrictions applies publisher-specific rules
|
// applyPublisherRestrictions applies publisher-specific rules
|
||||||
func (e *LicensePolicyEngine) applyPublisherRestrictions(facts *LicensedContentFacts, result *LicensePolicyResult) {
|
func (e *LicensePolicyEngine) applyPublisherRestrictions(facts *LicensedContentFacts, result *LicensePolicyResult) {
|
||||||
// DIN Media specific restrictions
|
|
||||||
if facts.Publisher == "DIN_MEDIA" {
|
if facts.Publisher == "DIN_MEDIA" {
|
||||||
if facts.AIUsePermitted != "YES" {
|
if facts.AIUsePermitted != "YES" {
|
||||||
// DIN Media explicitly prohibits AI use without license
|
|
||||||
if facts.OperationMode == "FULLTEXT_RAG" || facts.OperationMode == "TRAINING" {
|
if facts.OperationMode == "FULLTEXT_RAG" || facts.OperationMode == "TRAINING" {
|
||||||
result.Allowed = false
|
result.Allowed = false
|
||||||
result.EffectiveMode = "LINK_ONLY"
|
result.EffectiveMode = "LINK_ONLY"
|
||||||
@@ -394,7 +326,6 @@ func (e *LicensePolicyEngine) applyPublisherRestrictions(facts *LicensedContentF
|
|||||||
|
|
||||||
// checkDistributionScope checks if distribution scope matches license type
|
// checkDistributionScope checks if distribution scope matches license type
|
||||||
func (e *LicensePolicyEngine) checkDistributionScope(facts *LicensedContentFacts, result *LicensePolicyResult) {
|
func (e *LicensePolicyEngine) checkDistributionScope(facts *LicensedContentFacts, result *LicensePolicyResult) {
|
||||||
// Single workstation license with broad distribution
|
|
||||||
if facts.LicenseType == "SINGLE_WORKSTATION" {
|
if facts.LicenseType == "SINGLE_WORKSTATION" {
|
||||||
if facts.DistributionScope == "COMPANY_INTERNAL" ||
|
if facts.DistributionScope == "COMPANY_INTERNAL" ||
|
||||||
facts.DistributionScope == "SUBSIDIARIES" ||
|
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.LicenseType == "NETWORK_INTRANET" {
|
||||||
if facts.DistributionScope == "EXTERNAL_CUSTOMERS" {
|
if facts.DistributionScope == "EXTERNAL_CUSTOMERS" {
|
||||||
result.Gaps = append(result.Gaps, LicenseGap{
|
result.Gaps = append(result.Gaps, LicenseGap{
|
||||||
@@ -433,16 +363,16 @@ func (e *LicensePolicyEngine) checkDistributionScope(facts *LicensedContentFacts
|
|||||||
// CanIngestFulltext checks if fulltext ingestion is allowed
|
// CanIngestFulltext checks if fulltext ingestion is allowed
|
||||||
func (e *LicensePolicyEngine) CanIngestFulltext(facts *LicensedContentFacts) bool {
|
func (e *LicensePolicyEngine) CanIngestFulltext(facts *LicensedContentFacts) bool {
|
||||||
if !facts.Present {
|
if !facts.Present {
|
||||||
return true // No licensed content, no restrictions
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
switch facts.OperationMode {
|
switch facts.OperationMode {
|
||||||
case "LINK_ONLY":
|
case "LINK_ONLY":
|
||||||
return false // Only metadata/references
|
return false
|
||||||
case "NOTES_ONLY":
|
case "NOTES_ONLY":
|
||||||
return false // Only customer notes, not fulltext
|
return false
|
||||||
case "EXCERPT_ONLY":
|
case "EXCERPT_ONLY":
|
||||||
return false // Only short excerpts
|
return false
|
||||||
case "FULLTEXT_RAG":
|
case "FULLTEXT_RAG":
|
||||||
return facts.AIUsePermitted == "YES" && facts.ProofUploaded
|
return facts.AIUsePermitted == "YES" && facts.ProofUploaded
|
||||||
case "TRAINING":
|
case "TRAINING":
|
||||||
@@ -457,8 +387,6 @@ func (e *LicensePolicyEngine) CanIngestNotes(facts *LicensedContentFacts) bool {
|
|||||||
if !facts.Present {
|
if !facts.Present {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notes are allowed in most modes
|
|
||||||
return facts.OperationMode == "NOTES_ONLY" ||
|
return facts.OperationMode == "NOTES_ONLY" ||
|
||||||
facts.OperationMode == "EXCERPT_ONLY" ||
|
facts.OperationMode == "EXCERPT_ONLY" ||
|
||||||
facts.OperationMode == "FULLTEXT_RAG" ||
|
facts.OperationMode == "FULLTEXT_RAG" ||
|
||||||
@@ -471,40 +399,17 @@ func (e *LicensePolicyEngine) GetEffectiveMode(facts *LicensedContentFacts) stri
|
|||||||
return result.EffectiveMode
|
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
|
// DecideIngest returns the ingest decision for a document
|
||||||
func (e *LicensePolicyEngine) DecideIngest(facts *LicensedContentFacts) *LicenseIngestDecision {
|
func (e *LicensePolicyEngine) DecideIngest(facts *LicensedContentFacts) *LicenseIngestDecision {
|
||||||
result := e.Evaluate(facts)
|
result := e.Evaluate(facts)
|
||||||
|
|
||||||
decision := &LicenseIngestDecision{
|
return &LicenseIngestDecision{
|
||||||
AllowMetadata: true, // Metadata is always allowed
|
AllowMetadata: true, // Metadata is always allowed
|
||||||
AllowNotes: e.CanIngestNotes(facts),
|
AllowNotes: e.CanIngestNotes(facts),
|
||||||
AllowFulltext: e.CanIngestFulltext(facts),
|
AllowFulltext: e.CanIngestFulltext(facts),
|
||||||
Reason: result.Reason,
|
Reason: result.Reason,
|
||||||
EffectiveMode: result.EffectiveMode,
|
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
|
// FormatAuditEntry creates an audit entry for logging
|
||||||
|
|||||||
88
ai-compliance-sdk/internal/ucca/license_policy_types.go
Normal file
88
ai-compliance-sdk/internal/ucca/license_policy_types.go
Normal file
@@ -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"`
|
||||||
|
}
|
||||||
@@ -1,11 +1,5 @@
|
|||||||
package ucca
|
package ucca
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Constants / Enums
|
// Constants / Enums
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -53,15 +47,15 @@ type Domain string
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Industrie & Produktion
|
// Industrie & Produktion
|
||||||
DomainAutomotive Domain = "automotive"
|
DomainAutomotive Domain = "automotive"
|
||||||
DomainMechanicalEngineering Domain = "mechanical_engineering"
|
DomainMechanicalEngineering Domain = "mechanical_engineering"
|
||||||
DomainPlantEngineering Domain = "plant_engineering"
|
DomainPlantEngineering Domain = "plant_engineering"
|
||||||
DomainElectricalEngineering Domain = "electrical_engineering"
|
DomainElectricalEngineering Domain = "electrical_engineering"
|
||||||
DomainAerospace Domain = "aerospace"
|
DomainAerospace Domain = "aerospace"
|
||||||
DomainChemicals Domain = "chemicals"
|
DomainChemicals Domain = "chemicals"
|
||||||
DomainFoodBeverage Domain = "food_beverage"
|
DomainFoodBeverage Domain = "food_beverage"
|
||||||
DomainTextiles Domain = "textiles"
|
DomainTextiles Domain = "textiles"
|
||||||
DomainPackaging Domain = "packaging"
|
DomainPackaging Domain = "packaging"
|
||||||
|
|
||||||
// Energie & Versorgung
|
// Energie & Versorgung
|
||||||
DomainUtilities Domain = "utilities"
|
DomainUtilities Domain = "utilities"
|
||||||
@@ -79,7 +73,7 @@ const (
|
|||||||
DomainFacilityManagement Domain = "facility_management"
|
DomainFacilityManagement Domain = "facility_management"
|
||||||
|
|
||||||
// Gesundheit & Soziales
|
// Gesundheit & Soziales
|
||||||
DomainHealthcare Domain = "healthcare"
|
DomainHealthcare Domain = "healthcare"
|
||||||
DomainMedicalDevices Domain = "medical_devices"
|
DomainMedicalDevices Domain = "medical_devices"
|
||||||
DomainPharma Domain = "pharma"
|
DomainPharma Domain = "pharma"
|
||||||
DomainElderlyCare Domain = "elderly_care"
|
DomainElderlyCare Domain = "elderly_care"
|
||||||
@@ -98,10 +92,10 @@ const (
|
|||||||
DomainInvestment Domain = "investment"
|
DomainInvestment Domain = "investment"
|
||||||
|
|
||||||
// Handel & Logistik
|
// Handel & Logistik
|
||||||
DomainRetail Domain = "retail"
|
DomainRetail Domain = "retail"
|
||||||
DomainEcommerce Domain = "ecommerce"
|
DomainEcommerce Domain = "ecommerce"
|
||||||
DomainWholesale Domain = "wholesale"
|
DomainWholesale Domain = "wholesale"
|
||||||
DomainLogistics Domain = "logistics"
|
DomainLogistics Domain = "logistics"
|
||||||
|
|
||||||
// IT & Telekommunikation
|
// IT & Telekommunikation
|
||||||
DomainITServices Domain = "it_services"
|
DomainITServices Domain = "it_services"
|
||||||
@@ -177,347 +171,6 @@ const (
|
|||||||
TrainingNO TrainingAllowed = "NO"
|
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
|
// ExportFormat specifies the export format
|
||||||
type ExportFormat string
|
type ExportFormat string
|
||||||
|
|
||||||
|
|||||||
174
ai-compliance-sdk/internal/ucca/models_assessment.go
Normal file
174
ai-compliance-sdk/internal/ucca/models_assessment.go
Normal file
@@ -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"`
|
||||||
|
}
|
||||||
175
ai-compliance-sdk/internal/ucca/models_intake.go
Normal file
175
ai-compliance-sdk/internal/ucca/models_intake.go
Normal file
@@ -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"`
|
||||||
|
}
|
||||||
@@ -428,74 +428,3 @@ func (r *ObligationsRegistry) generateExecutiveSummary(overview *ManagementOblig
|
|||||||
return summary
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -408,103 +408,3 @@ func truncateString(s string, maxLen int) string {
|
|||||||
}
|
}
|
||||||
return s[:maxLen-3] + "..."
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
107
ai-compliance-sdk/internal/ucca/pdf_export_markdown.go
Normal file
107
ai-compliance-sdk/internal/ucca/pdf_export_markdown.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user