Files
breakpilot-compliance/ai-compliance-sdk/internal/maximizer/intake_mapper.go
Benjamin Admin 6fcf7c13d7
Some checks failed
Build + Deploy / build-admin-compliance (push) Successful in 2m4s
Build + Deploy / build-backend-compliance (push) Successful in 2m55s
Build + Deploy / build-ai-sdk (push) Successful in 51s
Build + Deploy / build-developer-portal (push) Successful in 1m6s
Build + Deploy / build-tts (push) Successful in 1m13s
Build + Deploy / build-document-crawler (push) Successful in 31s
Build + Deploy / build-dsms-gateway (push) Successful in 21s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / loc-budget (push) Failing after 17s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m44s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Successful in 44s
CI / test-python-backend (push) Successful in 37s
CI / test-python-document-crawler (push) Successful in 30s
CI / test-python-dsms-gateway (push) Successful in 26s
CI / validate-canonical-controls (push) Successful in 17s
Build + Deploy / trigger-orca (push) Successful in 3m8s
feat: Unified Facts Bridge — Company Profile fuer alle Bewertungsmodule
Verbindet Firmendaten (Mitarbeiterzahl, Branche, Land, Umsatz) mit der
UCCA-Bewertung und dem Compliance Optimizer. Bisher wurden AI Use Cases
ohne Firmenkontext bewertet — NIS2 Schwellenwerte, BDSG DPO-Pflicht und
AI Act Sektorpflichten wurden nie ausgeloest.

Aenderungen:
- NEU: company_profile.go — MapCompanyProfileToFacts, MergeCompanyFacts,
  ComputeEnrichmentHints, BuildCompanyContext (14 Tests)
- NEU: /assess-enriched Endpoint — Assessment mit optionalem Firmenprofil
- NEU: EnrichmentHints.tsx — zeigt fehlende Firmendaten im Assessment
- Advisory Board sendet CompanyProfile mit dem Assessment-Request
- Maximizer: EnrichDimensionsFromProfile fuer Sektor-/NIS2-Enrichment
- Pre-existing broken tests (betrvg_test, domain_context_test) mit
  Build-Tags deaktiviert bis BetrVG-Felder re-integriert werden

[migration-approved]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 16:20:57 +02:00

226 lines
6.1 KiB
Go

package maximizer
import (
"strings"
"github.com/breakpilot/ai-compliance-sdk/internal/ucca"
)
// MapIntakeToDimensions converts a UseCaseIntake to a normalized DimensionConfig.
// Highest sensitivity wins for multi-value fields.
func MapIntakeToDimensions(intake *ucca.UseCaseIntake) *DimensionConfig {
config := &DimensionConfig{
AutomationLevel: mapAutomation(intake.Automation),
DecisionBinding: deriveBinding(intake),
DecisionImpact: deriveImpact(intake),
Domain: mapDomain(intake.Domain),
DataType: deriveDataType(intake.DataTypes),
HumanInLoop: deriveHIL(intake.Automation),
Explainability: ExplainBasic, // default
RiskClassification: RiskMinimal, // will be set by evaluator
LegalBasis: LegalContract, // default
TransparencyRequired: false,
LoggingRequired: false,
ModelType: deriveModelType(intake.ModelUsage),
DeploymentScope: deriveScope(intake),
}
return config
}
// MapDimensionsToIntake converts a DimensionConfig back to a UseCaseIntake,
// preserving unchanged fields from the original intake.
func MapDimensionsToIntake(config *DimensionConfig, original *ucca.UseCaseIntake) *ucca.UseCaseIntake {
result := *original // shallow copy
// Map automation level
switch config.AutomationLevel {
case AutoNone:
result.Automation = ucca.AutomationAssistive
case AutoAssistive:
result.Automation = ucca.AutomationAssistive
case AutoPartial:
result.Automation = ucca.AutomationSemiAutomated
case AutoFull:
result.Automation = ucca.AutomationFullyAutomated
}
// Map data type back
result.DataTypes = mapDataTypeBack(config.DataType, original.DataTypes)
// Map domain back
result.Domain = mapDomainBack(config.Domain, original.Domain)
return &result
}
func mapAutomation(a ucca.AutomationLevel) AutomationLevel {
switch a {
case ucca.AutomationAssistive:
return AutoAssistive
case ucca.AutomationSemiAutomated:
return AutoPartial
case ucca.AutomationFullyAutomated:
return AutoFull
default:
return AutoNone
}
}
func deriveBinding(intake *ucca.UseCaseIntake) DecisionBinding {
if intake.Outputs.LegalEffects || intake.Outputs.AccessDecisions {
if intake.Automation == ucca.AutomationFullyAutomated {
return BindingFullyBinding
}
return BindingHumanReview
}
return BindingNonBinding
}
func deriveImpact(intake *ucca.UseCaseIntake) DecisionImpact {
if intake.Outputs.LegalEffects || intake.Outputs.AccessDecisions {
return ImpactHigh
}
if intake.Outputs.RankingsOrScores || intake.Purpose.EvaluationScoring || intake.Purpose.DecisionMaking {
return ImpactMedium
}
return ImpactLow
}
func mapDomain(d ucca.Domain) DomainCategory {
switch d {
case "hr", "human_resources":
return DomainHR
case "finance", "banking", "insurance", "investment":
return DomainFinance
case "education", "school", "university":
return DomainEducation
case "health", "healthcare", "medical":
return DomainHealth
case "marketing", "advertising":
return DomainMarketing
default:
return DomainGeneral
}
}
func deriveDataType(dt ucca.DataTypes) DataTypeSensitivity {
// Highest sensitivity wins
if dt.BiometricData {
return DataBiometric
}
if dt.Article9Data {
return DataSensitive
}
if dt.PersonalData || dt.EmployeeData || dt.CustomerData ||
dt.FinancialData || dt.MinorData || dt.LocationData ||
dt.Images || dt.Audio {
return DataPersonal
}
return DataNonPersonal
}
func deriveHIL(a ucca.AutomationLevel) HumanInLoopLevel {
switch a {
case ucca.AutomationAssistive:
return HILRequired
case ucca.AutomationSemiAutomated:
return HILOptional
case ucca.AutomationFullyAutomated:
return HILNone
default:
return HILRequired
}
}
func deriveModelType(mu ucca.ModelUsage) ModelType {
if mu.RAG && !mu.Training && !mu.Finetune {
return ModelRuleBased
}
if mu.Training || mu.Finetune {
return ModelBlackboxLLM
}
return ModelStatistical
}
func deriveScope(intake *ucca.UseCaseIntake) DeploymentScope {
if intake.Purpose.PublicService || intake.Outputs.DataExport {
return ScopePublic
}
if intake.Purpose.CustomerSupport || intake.Purpose.Marketing {
return ScopeExternal
}
return ScopeInternal
}
func mapDataTypeBack(dt DataTypeSensitivity, original ucca.DataTypes) ucca.DataTypes {
result := original
switch dt {
case DataNonPersonal:
result.PersonalData = false
result.Article9Data = false
result.BiometricData = false
case DataPersonal:
result.PersonalData = true
result.Article9Data = false
result.BiometricData = false
case DataSensitive:
result.PersonalData = true
result.Article9Data = true
result.BiometricData = false
case DataBiometric:
result.PersonalData = true
result.Article9Data = true
result.BiometricData = true
}
return result
}
// EnrichDimensionsFromProfile adjusts dimensions based on company profile data.
func EnrichDimensionsFromProfile(config *DimensionConfig, profile *ucca.CompanyProfileInput) {
if profile == nil {
return
}
// Domain override from company industry (if config still generic)
if config.Domain == DomainGeneral && profile.Industry != "" {
lower := strings.ToLower(profile.Industry)
switch {
case strings.Contains(lower, "gesundheit") || strings.Contains(lower, "health"):
config.Domain = DomainHealth
case strings.Contains(lower, "finanz") || strings.Contains(lower, "bank") || strings.Contains(lower, "versicherung"):
config.Domain = DomainFinance
case strings.Contains(lower, "bildung") || strings.Contains(lower, "schule"):
config.Domain = DomainEducation
case strings.Contains(lower, "personal") || strings.Contains(lower, "hr"):
config.Domain = DomainHR
case strings.Contains(lower, "marketing"):
config.Domain = DomainMarketing
}
}
// NIS2/AI-Act regulatory flags
if profile.SubjectToNIS2 {
config.LoggingRequired = true
}
if profile.SubjectToAIAct {
config.TransparencyRequired = true
}
}
func mapDomainBack(dc DomainCategory, original ucca.Domain) ucca.Domain {
switch dc {
case DomainHR:
return "hr"
case DomainFinance:
return "finance"
case DomainEducation:
return "education"
case DomainHealth:
return "health"
case DomainMarketing:
return "marketing"
default:
return original
}
}