package ucca import "strings" // CompanyProfileInput contains the regulatory-relevant subset of a company profile. // Mirrors the Python CompanyProfileRequest schema for the fields that affect assessment. type CompanyProfileInput struct { CompanyName string `json:"company_name,omitempty"` LegalForm string `json:"legal_form,omitempty"` Industry string `json:"industry,omitempty"` EmployeeCount string `json:"employee_count,omitempty"` // "1-9", "10-49", "50-249", "250-999", "1000+" AnnualRevenue string `json:"annual_revenue,omitempty"` // "< 2 Mio", "2-10 Mio", "10-50 Mio", "50+ Mio" HeadquartersCountry string `json:"headquarters_country,omitempty"` IsDataController bool `json:"is_data_controller"` IsDataProcessor bool `json:"is_data_processor"` UsesAI bool `json:"uses_ai"` AIUseCases []string `json:"ai_use_cases,omitempty"` DPOName *string `json:"dpo_name,omitempty"` SubjectToNIS2 bool `json:"subject_to_nis2"` SubjectToAIAct bool `json:"subject_to_ai_act"` SubjectToISO27001 bool `json:"subject_to_iso27001"` } // EnrichmentHint tells the frontend which missing company data would improve the assessment. type EnrichmentHint struct { Field string `json:"field"` Label string `json:"label"` Impact string `json:"impact"` Regulation string `json:"regulation"` Priority string `json:"priority"` // "high", "medium", "low" } // CompanyContextSummary is a compact view of the company's regulatory position. type CompanyContextSummary struct { SizeCategory string `json:"size_category"` NIS2Applicable bool `json:"nis2_applicable"` DPORequired bool `json:"dpo_required"` Sector string `json:"sector"` Country string `json:"country"` } // MapCompanyProfileToFacts converts a company profile to UnifiedFacts. func MapCompanyProfileToFacts(profile *CompanyProfileInput) *UnifiedFacts { if profile == nil { return NewUnifiedFacts() } facts := NewUnifiedFacts() // Organization facts.Organization.Name = profile.CompanyName facts.Organization.LegalForm = profile.LegalForm facts.Organization.EmployeeCount = parseEmployeeRangeGo(profile.EmployeeCount) facts.Organization.AnnualRevenue = parseRevenueRangeGo(profile.AnnualRevenue) if profile.HeadquartersCountry != "" { facts.Organization.Country = profile.HeadquartersCountry } facts.Organization.EUMember = isEUCountry(profile.HeadquartersCountry) facts.Organization.CalculateSizeCategory() // Sector if profile.Industry != "" { facts.MapDomainToSector(mapIndustryToDomain(profile.Industry)) } // Data Protection facts.DataProtection.IsController = profile.IsDataController facts.DataProtection.IsProcessor = profile.IsDataProcessor facts.DataProtection.ProcessesPersonalData = true // assumed for all compliance customers facts.DataProtection.OffersToEU = facts.Organization.EUMember // DPO requirement: BDSG §6 — ≥20 employees processing personal data in DE if facts.Organization.Country == "DE" && facts.Organization.EmployeeCount >= 20 && facts.DataProtection.ProcessesPersonalData { facts.DataProtection.RequiresDSBByLaw = true } // Personnel if profile.DPOName != nil && *profile.DPOName != "" { facts.Personnel.HasDPO = true } // AI Usage facts.AIUsage.UsesAI = profile.UsesAI if len(profile.AIUseCases) > 0 { facts.AIUsage.IsAIDeployer = true } // IT Security facts.ITSecurity.ISO27001Certified = profile.SubjectToISO27001 // NIS2 flags if profile.SubjectToNIS2 { if facts.Sector.NIS2Classification == "" { facts.Sector.NIS2Classification = "wichtige_einrichtung" } } return facts } // MergeCompanyFactsIntoIntakeFacts merges company-level facts with use-case-level facts. // Company wins for: Organization, Sector, Personnel, Financial, ITSecurity, SupplyChain. // Intake wins for: DataProtection details, AIUsage details, UCCAFacts. func MergeCompanyFactsIntoIntakeFacts(companyFacts, intakeFacts *UnifiedFacts) *UnifiedFacts { if companyFacts == nil { return intakeFacts } if intakeFacts == nil { return companyFacts } merged := NewUnifiedFacts() // Company-level fields (from company profile) merged.Organization = companyFacts.Organization merged.Sector = companyFacts.Sector merged.Personnel = companyFacts.Personnel merged.Financial = companyFacts.Financial merged.ITSecurity = companyFacts.ITSecurity merged.SupplyChain = companyFacts.SupplyChain // Use-case-level fields (from intake) merged.DataProtection = intakeFacts.DataProtection merged.AIUsage = intakeFacts.AIUsage merged.UCCAFacts = intakeFacts.UCCAFacts // Preserve company-level data protection facts that intake doesn't override merged.DataProtection.IsController = companyFacts.DataProtection.IsController merged.DataProtection.IsProcessor = companyFacts.DataProtection.IsProcessor merged.DataProtection.RequiresDSBByLaw = companyFacts.DataProtection.RequiresDSBByLaw merged.DataProtection.OffersToEU = companyFacts.DataProtection.OffersToEU // Preserve company AI flags alongside intake AI flags if companyFacts.AIUsage.UsesAI { merged.AIUsage.UsesAI = true } return merged } // ComputeEnrichmentHints returns hints for fields that would improve the assessment. func ComputeEnrichmentHints(profile *CompanyProfileInput) []EnrichmentHint { if profile == nil { return allCriticalHints() } var hints []EnrichmentHint if profile.EmployeeCount == "" { hints = append(hints, EnrichmentHint{ Field: "employee_count", Label: "Mitarbeiterzahl", Impact: "NIS2-Schwellenwert (>=50 MA) und BDSG DPO-Pflicht (>=20 MA in DE) koennen nicht geprueft werden", Regulation: "NIS2, BDSG §6", Priority: "high", }) } if profile.AnnualRevenue == "" { hints = append(hints, EnrichmentHint{ Field: "annual_revenue", Label: "Jahresumsatz", Impact: "NIS2-Schwellenwert (>=10 Mio EUR) und KMU-Einstufung nicht pruefbar", Regulation: "NIS2, EU KMU-Definition", Priority: "high", }) } if profile.Industry == "" { hints = append(hints, EnrichmentHint{ Field: "industry", Label: "Branche", Impact: "NIS2 Annex I/II Sektor-Klassifikation und AI Act Hochrisiko-Sektoren nicht bestimmbar", Regulation: "NIS2, AI Act Annex III", Priority: "high", }) } if profile.HeadquartersCountry == "" { hints = append(hints, EnrichmentHint{ Field: "headquarters_country", Label: "Land des Hauptsitzes", Impact: "BDSG-spezifische Regeln (z.B. HR-Daten §26 BDSG) koennen nicht angewandt werden", Regulation: "BDSG", Priority: "medium", }) } if profile.DPOName == nil || *profile.DPOName == "" { hints = append(hints, EnrichmentHint{ Field: "dpo_name", Label: "Datenschutzbeauftragter", Impact: "DPO-Pflicht kann nicht gegen vorhandene Benennung abgeglichen werden", Regulation: "DSGVO Art. 37, BDSG §6", Priority: "medium", }) } return hints } // BuildCompanyContext creates a summary of the company's regulatory position. func BuildCompanyContext(profile *CompanyProfileInput) *CompanyContextSummary { if profile == nil { return nil } facts := MapCompanyProfileToFacts(profile) return &CompanyContextSummary{ SizeCategory: facts.Organization.SizeCategory, NIS2Applicable: facts.Organization.MeetsNIS2SizeThreshold() || profile.SubjectToNIS2, DPORequired: facts.DataProtection.RequiresDSBByLaw, Sector: facts.Sector.PrimarySector, Country: facts.Organization.Country, } } func allCriticalHints() []EnrichmentHint { return []EnrichmentHint{ {Field: "employee_count", Label: "Mitarbeiterzahl", Impact: "NIS2/BDSG DPO nicht pruefbar", Regulation: "NIS2, BDSG", Priority: "high"}, {Field: "annual_revenue", Label: "Jahresumsatz", Impact: "NIS2/KMU nicht pruefbar", Regulation: "NIS2", Priority: "high"}, {Field: "industry", Label: "Branche", Impact: "Sektor-Klassifikation nicht moeglich", Regulation: "NIS2, AI Act", Priority: "high"}, {Field: "headquarters_country", Label: "Land", Impact: "BDSG-Regeln nicht anwendbar", Regulation: "BDSG", Priority: "medium"}, } } // --- Parsers (matching TypeScript parseEmployeeRange/parseRevenueRange) --- func parseEmployeeRangeGo(r string) int { switch r { case "1-9": return 5 case "10-49": return 30 case "50-249": return 150 case "250-999": return 625 case "1000+": return 1500 default: return 0 } } func parseRevenueRangeGo(r string) float64 { switch r { case "< 2 Mio": return 1_000_000 case "2-10 Mio": return 6_000_000 case "10-50 Mio": return 30_000_000 case "50+ Mio": return 75_000_000 default: return 0 } } func isEUCountry(code string) bool { eu := map[string]bool{ "DE": true, "AT": true, "FR": true, "IT": true, "ES": true, "NL": true, "BE": true, "LU": true, "IE": true, "PT": true, "GR": true, "FI": true, "SE": true, "DK": true, "PL": true, "CZ": true, "SK": true, "HU": true, "RO": true, "BG": true, "HR": true, "SI": true, "LT": true, "LV": true, "EE": true, "CY": true, "MT": true, } return eu[strings.ToUpper(code)] } func mapIndustryToDomain(industry string) string { lower := strings.ToLower(industry) switch { case strings.Contains(lower, "gesundheit") || strings.Contains(lower, "health") || strings.Contains(lower, "pharma"): return "healthcare" case strings.Contains(lower, "finanz") || strings.Contains(lower, "bank") || strings.Contains(lower, "versicherung"): return "finance" case strings.Contains(lower, "bildung") || strings.Contains(lower, "schule") || strings.Contains(lower, "universit"): return "education" case strings.Contains(lower, "energie") || strings.Contains(lower, "energy"): return "energy" case strings.Contains(lower, "logistik") || strings.Contains(lower, "transport"): return "logistics" case strings.Contains(lower, "it") || strings.Contains(lower, "software") || strings.Contains(lower, "tech"): return "it_services" default: return lower } }