diff --git a/admin-compliance/app/sdk/gap-analysis/_components/IstAssessment.tsx b/admin-compliance/app/sdk/gap-analysis/_components/IstAssessment.tsx
new file mode 100644
index 0000000..5dd20aa
--- /dev/null
+++ b/admin-compliance/app/sdk/gap-analysis/_components/IstAssessment.tsx
@@ -0,0 +1,166 @@
+'use client'
+
+import React from 'react'
+
+const NORMS = [
+ { value: 'ISO12100', label: 'ISO 12100 (Maschinensicherheit)' },
+ { value: 'ENISO13849', label: 'EN ISO 13849 (Sicherheitsfunktionen)' },
+ { value: 'IEC61508', label: 'IEC 61508 (Funktionale Sicherheit)' },
+ { value: 'IEC62443', label: 'IEC 62443 (Industrielle Cybersecurity)' },
+ { value: 'ISO27001', label: 'ISO 27001 (Informationssicherheit)' },
+ { value: 'ISO27002', label: 'ISO 27002 (Security Controls)' },
+ { value: 'EN61326', label: 'EN 61326 (EMV)' },
+ { value: 'EN62368', label: 'EN 62368 (Audio/Video/IT-Sicherheit)' },
+ { value: 'IEC60204', label: 'IEC 60204 (Elektrische Ausruestung)' },
+ { value: 'ISO13485', label: 'ISO 13485 (Medizinprodukte QM)' },
+ { value: 'ISO14971', label: 'ISO 14971 (Risikomanagement Medizin)' },
+ { value: 'IEC62304', label: 'IEC 62304 (Medizin-Software Lifecycle)' },
+ { value: 'ISO9001', label: 'ISO 9001 (Qualitaetsmanagement)' },
+ { value: 'ISO22301', label: 'ISO 22301 (Business Continuity)' },
+ { value: 'PCIDSS', label: 'PCI DSS (Zahlungssicherheit)' },
+ { value: 'EN50581', label: 'EN 50581 (RoHS/REACH)' },
+ { value: 'ASPICE', label: 'ASPICE (Automotive Software)' },
+]
+
+interface IstData {
+ applied_norms: string[]
+ has_risk_assessment: boolean
+ has_technical_file: boolean
+ has_operating_manual: boolean
+ has_sbom: boolean
+ has_vuln_management: boolean
+ has_update_mechanism: boolean
+ has_incident_response: boolean
+ has_supply_chain_mgmt: boolean
+ ce_marking_since: string
+ product_age: string
+}
+
+interface Props {
+ data: IstData
+ onChange: (data: IstData) => void
+}
+
+export function IstAssessment({ data, onChange }: Props) {
+ const update = (field: string, value: unknown) => {
+ onChange({ ...data, [field]: value })
+ }
+
+ const toggleNorm = (norm: string) => {
+ const norms = data.applied_norms.includes(norm)
+ ? data.applied_norms.filter(n => n !== norm)
+ : [...data.applied_norms, norm]
+ update('applied_norms', norms)
+ }
+
+ return (
+
+
+
+ Geben Sie an was Sie bereits haben. Je mehr wir wissen, desto
+ praeziser ist die Gap-Analyse. Controls die bereits erfuellt sind
+ werden automatisch als "erledigt" markiert.
+
+
+
+ {/* CE-Kennzeichnung */}
+
+
CE-Kennzeichnung
+
+
+ CE seit (Jahr)
+ update('ce_marking_since', e.target.value)}
+ placeholder="z.B. 2016"
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
+ />
+
+
+ Produktalter
+ update('product_age', e.target.value)}
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
+ >
+ Bitte waehlen
+ Neues Produkt (noch nicht am Markt)
+ 1 Jahr
+ 2-3 Jahre
+ 4-5 Jahre
+ 6-10 Jahre
+ Ueber 10 Jahre
+
+
+
+
+
+ {/* Angewandte Normen */}
+
+
Angewandte Normen
+
+ {NORMS.map(n => (
+ toggleNorm(n.value)}
+ className={`px-3 py-1.5 rounded-full text-xs border transition-colors ${
+ data.applied_norms.includes(n.value)
+ ? 'bg-green-100 border-green-400 text-green-800'
+ : 'border-gray-200 text-gray-600 hover:bg-gray-50'
+ }`}
+ >
+ {n.label}
+
+ ))}
+
+
+
+ {/* Bestehende Dokumentation */}
+
+
Bestehende Dokumentation
+
+ {[
+ { field: 'has_risk_assessment', label: 'Risikobeurteilung vorhanden' },
+ { field: 'has_technical_file', label: 'Technische Dokumentation vorhanden' },
+ { field: 'has_operating_manual', label: 'Betriebsanleitung vorhanden' },
+ { field: 'has_sbom', label: 'SBOM (Software Bill of Materials)' },
+ ].map(item => (
+
+ )[item.field] as boolean}
+ onChange={e => update(item.field, e.target.checked)}
+ className="w-4 h-4 rounded border-gray-300 text-green-600"
+ />
+ {item.label}
+
+ ))}
+
+
+
+ {/* Bestehende Prozesse */}
+
+
Bestehende Prozesse
+
+ {[
+ { field: 'has_vuln_management', label: 'Schwachstellenmanagement' },
+ { field: 'has_update_mechanism', label: 'Software-Update-Mechanismus' },
+ { field: 'has_incident_response', label: 'Incident Response Prozess' },
+ { field: 'has_supply_chain_mgmt', label: 'Lieferketten-Management' },
+ ].map(item => (
+
+ )[item.field] as boolean}
+ onChange={e => update(item.field, e.target.checked)}
+ className="w-4 h-4 rounded border-gray-300 text-green-600"
+ />
+ {item.label}
+
+ ))}
+
+
+
+ )
+}
diff --git a/admin-compliance/app/sdk/gap-analysis/_components/ProductWizard.tsx b/admin-compliance/app/sdk/gap-analysis/_components/ProductWizard.tsx
index f78137d..03f1a11 100644
--- a/admin-compliance/app/sdk/gap-analysis/_components/ProductWizard.tsx
+++ b/admin-compliance/app/sdk/gap-analysis/_components/ProductWizard.tsx
@@ -1,6 +1,7 @@
'use client'
import React, { useState } from 'react'
+import { IstAssessment } from './IstAssessment'
const PRODUCT_TYPES = [
{ value: 'iot', label: 'IoT / Connected Device' },
@@ -60,6 +61,20 @@ export function ProductWizard({ onAnalyze, loading }: Props) {
const [usesAI, setUsesAI] = useState(false)
const [processesPersonalData, setProcessesPersonalData] = useState(false)
const [isCriticalInfra, setIsCriticalInfra] = useState(false)
+ const [step, setStep] = useState(1)
+ const [istData, setIstData] = useState({
+ applied_norms: [] as string[],
+ has_risk_assessment: false,
+ has_technical_file: false,
+ has_operating_manual: false,
+ has_sbom: false,
+ has_vuln_management: false,
+ has_update_mechanism: false,
+ has_incident_response: false,
+ has_supply_chain_mgmt: false,
+ ce_marking_since: '',
+ product_age: '',
+ })
const toggleArrayValue = (
arr: string[],
@@ -83,11 +98,59 @@ export function ProductWizard({ onAnalyze, loading }: Props) {
processes_personal_data: processesPersonalData,
is_critical_infra_supplier: isCriticalInfra,
existing_certifications: certifications,
+ ...istData,
})
}
return (
+ {/* Step Indicator */}
+
+ setStep(1)}
+ className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium ${
+ step === 1 ? 'bg-blue-100 text-blue-700' : 'text-gray-500 hover:bg-gray-50'
+ }`}
+ >
+ 1
+ Produkt beschreiben
+
+ →
+ productType ? setStep(2) : null}
+ className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium ${
+ step === 2 ? 'bg-blue-100 text-blue-700' : 'text-gray-500 hover:bg-gray-50'
+ } ${!productType ? 'opacity-50 cursor-not-allowed' : ''}`}
+ >
+ 2
+ IST-Zustand
+
+
+
+ {step === 2 && (
+ <>
+
+
+ setStep(1)}
+ className="px-6 py-3 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50"
+ >
+ Zurueck
+
+
+ {loading ? 'Analyse laeuft...' : 'Gap-Analyse starten'}
+
+
+ >
+ )}
+
+ {step === 1 && (<>
{/* Produktname */}
@@ -225,14 +288,15 @@ export function ProductWizard({ onAnalyze, loading }: Props) {
- {/* Submit */}
+ {/* Next Step */}
setStep(2)}
+ disabled={!productType}
className="w-full py-3 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors"
>
- {loading ? 'Analyse laeuft...' : 'Gap-Analyse starten'}
+ Weiter: IST-Zustand erfassen →
+ >)}
)
}
diff --git a/ai-compliance-sdk/internal/gap/gap_engine.go b/ai-compliance-sdk/internal/gap/gap_engine.go
index b16f3fa..043f9ad 100644
--- a/ai-compliance-sdk/internal/gap/gap_engine.go
+++ b/ai-compliance-sdk/internal/gap/gap_engine.go
@@ -38,7 +38,7 @@ func (e *Engine) Analyze(profile *ProductProfile) (*GapReport, error) {
// Step 4: Assess gaps
gaps := make([]GapItem, 0, len(mcGroups))
for _, mc := range mcGroups {
- status := e.assessGapStatus(mc, profile.ExistingCertifications)
+ status := e.assessGapStatus(mc, profile)
item := GapItem{
MCID: mc.MasterControlID,
MCName: mc.CanonicalName,
@@ -77,27 +77,80 @@ func (e *Engine) Analyze(profile *ProductProfile) (*GapReport, error) {
return report, nil
}
-// assessGapStatus determines if a MC is fulfilled based on existing certs.
-func (e *Engine) assessGapStatus(mc MCGroup, certs []string) GapStatus {
- // If customer has ISO 27001, many security controls are likely fulfilled
- for _, cert := range certs {
+// assessGapStatus determines if a MC is fulfilled based on IST-Zustand:
+// IACE project data, applied norms, certifications, and existing processes.
+func (e *Engine) assessGapStatus(mc MCGroup, profile *ProductProfile) GapStatus {
+ name := mc.CanonicalName
+
+ // A) IACE-Projekt vorhanden → aus verified Mitigations ableiten
+ if profile.IACEProjectID != nil {
+ status := e.store.CheckIACECoverage(*profile.IACEProjectID, name)
+ if status == "verified" {
+ return GapFulfilled
+ }
+ if status == "implemented" {
+ return GapPartial
+ }
+ }
+
+ // B) Bestehende Zertifizierungen
+ for _, cert := range profile.ExistingCertifications {
switch cert {
- case "ISO27001":
- if isSecurityTopic(mc.CanonicalName) {
- return GapPartial // Likely partially covered
- }
case "CE":
- if isMachineryTopic(mc.CanonicalName) {
+ if isMachineryTopic(name) {
return GapFulfilled
}
+ case "ISO27001":
+ if isSecurityTopic(name) {
+ return GapPartial
+ }
case "SOC2":
- if isSecurityTopic(mc.CanonicalName) {
+ if isSecurityTopic(name) {
+ return GapPartial
+ }
+ case "ISO13485":
+ if contains(name, "risk_management") || contains(name, "documentation") {
return GapPartial
}
}
}
- // Default: missing (customer must verify)
+ // C) Angewandte Normen → Controls als fulfilled erkennen
+ if normCoversControl(profile.AppliedNorms, name) {
+ return GapFulfilled
+ }
+
+ // D) IST-Felder direkt matchen
+ if profile.HasSBOM && contains(name, "asset_management_inventory") {
+ return GapFulfilled
+ }
+ if profile.HasVulnManagement && contains(name, "vulnerability") {
+ return GapFulfilled
+ }
+ if profile.HasUpdateMechanism && contains(name, "patch_management") {
+ return GapFulfilled
+ }
+ if profile.HasIncidentResponse && contains(name, "incident") {
+ return GapFulfilled
+ }
+ if profile.HasRiskAssessment && contains(name, "risk_management") {
+ return GapFulfilled
+ }
+ if profile.HasTechnicalFile && contains(name, "documentation") {
+ return GapFulfilled
+ }
+ if profile.HasOperatingManual && contains(name, "operating_instructions") {
+ return GapFulfilled
+ }
+ if profile.HasSupplyChainMgmt && contains(name, "third_party_management") {
+ return GapFulfilled
+ }
+
+ // E) CE-Kennzeichnung vorhanden → Produktsicherheit fulfilled
+ if profile.CEMarkingSince != nil && isMachineryTopic(name) {
+ return GapFulfilled
+ }
+
return GapMissing
}
diff --git a/ai-compliance-sdk/internal/gap/models.go b/ai-compliance-sdk/internal/gap/models.go
index 224e42f..4862da7 100644
--- a/ai-compliance-sdk/internal/gap/models.go
+++ b/ai-compliance-sdk/internal/gap/models.go
@@ -52,6 +52,30 @@ type ProductProfile struct {
// Existing certifications (reduces gap count)
ExistingCertifications []string `json:"existing_certifications" db:"-"` // ISO27001, CE, SOC2
+ // ── IST-Zustand (was hat der Hersteller bereits?) ──────────────
+
+ // Verbindung zu bestehendem IACE Projekt
+ IACEProjectID *uuid.UUID `json:"iace_project_id" db:"iace_project_id"`
+
+ // Angewandte Normen
+ AppliedNorms []string `json:"applied_norms" db:"-"` // ISO12100, EN61326, EN62368
+
+ // Bestehende Dokumentation
+ HasRiskAssessment bool `json:"has_risk_assessment" db:"has_risk_assessment"`
+ HasTechnicalFile bool `json:"has_technical_file" db:"has_technical_file"`
+ HasOperatingManual bool `json:"has_operating_manual" db:"has_operating_manual"`
+ HasSBOM bool `json:"has_sbom" db:"has_sbom"`
+
+ // Bestehende Prozesse
+ HasVulnManagement bool `json:"has_vuln_management" db:"has_vuln_management"`
+ HasUpdateMechanism bool `json:"has_update_mechanism" db:"has_update_mechanism"`
+ HasIncidentResponse bool `json:"has_incident_response" db:"has_incident_response"`
+ HasSupplyChainMgmt bool `json:"has_supply_chain_mgmt" db:"has_supply_chain_mgmt"`
+
+ // CE/Produktsicherheit
+ CEMarkingSince *string `json:"ce_marking_since" db:"ce_marking_since"`
+ ProductAge string `json:"product_age" db:"product_age"`
+
// Metadata
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
diff --git a/ai-compliance-sdk/internal/gap/norm_mapping.go b/ai-compliance-sdk/internal/gap/norm_mapping.go
new file mode 100644
index 0000000..2dce62c
--- /dev/null
+++ b/ai-compliance-sdk/internal/gap/norm_mapping.go
@@ -0,0 +1,73 @@
+package gap
+
+// NormToControlMapping maps applied norms to MC topic prefixes they cover.
+// If a manufacturer has applied a norm, all matching MC topics are "fulfilled".
+var NormToControlMapping = map[string][]string{
+ // Machine Safety
+ "ISO12100": {"risk_management_assessment", "risk_management_documentation", "product_safety"},
+ "ENISO13849": {"product_safety", "risk_management_assessment", "secure_development"},
+ "IEC61508": {"product_safety", "risk_management", "secure_development"},
+ "IEC62061": {"product_safety", "risk_management"},
+
+ // EMC / Electrical Safety
+ "EN61326": {"network_security", "physical_security"},
+ "EN62368": {"physical_security", "product_safety"},
+ "IEC60204": {"physical_security", "product_safety"},
+
+ // Information Security
+ "ISO27001": {
+ "access_control", "encryption", "incident", "audit_logging",
+ "vulnerability", "patch_management", "risk_management",
+ "human_resources_security", "physical_security", "backup",
+ "disaster_recovery", "change_management", "asset_management",
+ "monitoring", "network_security",
+ },
+ "ISO27002": {
+ "access_control", "encryption", "audit_logging",
+ "vulnerability", "patch_management",
+ },
+
+ // Industrial Cybersecurity
+ "IEC62443": {
+ "network_security", "network_segmentation", "access_control",
+ "monitoring", "vulnerability", "patch_management",
+ "incident", "secure_development",
+ },
+
+ // Medical Devices
+ "ISO13485": {"risk_management", "documentation", "change_management", "training"},
+ "IEC60601": {"physical_security", "product_safety"},
+ "ISO14971": {"risk_management_assessment", "risk_management_documentation"},
+ "IEC62304": {"secure_development", "change_management", "documentation"},
+
+ // Crypto/Fintech
+ "ISO22301": {"disaster_recovery", "backup", "incident"},
+ "PCIDSS": {"encryption", "access_control", "audit_logging", "vulnerability", "network_segmentation"},
+
+ // Quality / Environmental
+ "ISO9001": {"change_management", "documentation", "training", "compliance_audit"},
+ "ISO14001": {"compliance_audit", "documentation", "risk_management"},
+
+ // Product Safety / RoHS / REACH
+ "EN50581": {"supply_chain_due_diligence", "product_safety"},
+
+ // Functional Safety (software)
+ "ASPICE": {"secure_development", "change_management", "documentation"},
+ "ISO26262": {"secure_development", "risk_management", "product_safety"},
+}
+
+// normCoversControl checks if any applied norm covers a given MC topic.
+func normCoversControl(appliedNorms []string, mcTopic string) bool {
+ for _, norm := range appliedNorms {
+ topics, ok := NormToControlMapping[norm]
+ if !ok {
+ continue
+ }
+ for _, topic := range topics {
+ if contains(mcTopic, topic) {
+ return true
+ }
+ }
+ }
+ return false
+}
diff --git a/ai-compliance-sdk/internal/gap/store.go b/ai-compliance-sdk/internal/gap/store.go
index e725014..9bc7d18 100644
--- a/ai-compliance-sdk/internal/gap/store.go
+++ b/ai-compliance-sdk/internal/gap/store.go
@@ -35,19 +35,29 @@ func (s *Store) CreateProfile(p *ProductProfile) error {
marketsJSON, _ := json.Marshal(p.Markets)
certsJSON, _ := json.Marshal(p.ExistingCertifications)
+ normsJSON, _ := json.Marshal(p.AppliedNorms)
+
_, err := s.pool.Exec(ctx, `
INSERT INTO compliance.gap_projects
(id, tenant_id, name, description, product_type,
technologies, data_processing, markets,
connected_to_internet, has_software_updates, uses_ai,
processes_personal_data, is_critical_infra_supplier,
- existing_certifications, created_at, updated_at)
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)`,
+ existing_certifications, applied_norms,
+ has_risk_assessment, has_technical_file, has_operating_manual, has_sbom,
+ has_vuln_management, has_update_mechanism, has_incident_response, has_supply_chain_mgmt,
+ ce_marking_since, product_age, iace_project_id,
+ created_at, updated_at)
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28)`,
p.ID, p.TenantID, p.Name, p.Description, p.ProductType,
techJSON, dataJSON, marketsJSON,
p.ConnectedToInternet, p.HasSoftwareUpdates, p.UsesAI,
p.ProcessesPersonalData, p.IsCriticalInfraSupplier,
- certsJSON, p.CreatedAt, p.UpdatedAt,
+ certsJSON, normsJSON,
+ p.HasRiskAssessment, p.HasTechnicalFile, p.HasOperatingManual, p.HasSBOM,
+ p.HasVulnManagement, p.HasUpdateMechanism, p.HasIncidentResponse, p.HasSupplyChainMgmt,
+ p.CEMarkingSince, p.ProductAge, p.IACEProjectID,
+ p.CreatedAt, p.UpdatedAt,
)
return err
}
@@ -227,6 +237,58 @@ func sourceToRegID(source string) RegulationID {
}
}
+// CheckIACECoverage checks if an IACE project has verified mitigations
+// covering the given MC topic.
+func (s *Store) CheckIACECoverage(projectID uuid.UUID, mcTopic string) string {
+ ctx := context.Background()
+
+ // Map MC topics to IACE hazard categories
+ iaceCategory := mcTopicToIACECategory(mcTopic)
+ if iaceCategory == "" {
+ return ""
+ }
+
+ var verifiedCount, implementedCount int
+ err := s.pool.QueryRow(ctx, `
+ SELECT
+ COUNT(CASE WHEN m.status = 'verified' THEN 1 END),
+ COUNT(CASE WHEN m.status = 'implemented' THEN 1 END)
+ FROM iace_mitigations m
+ JOIN iace_hazards h ON h.id = m.hazard_id
+ WHERE h.project_id = $1
+ AND (h.category ILIKE $2 OR h.sub_category ILIKE $2)`,
+ projectID, "%"+iaceCategory+"%",
+ ).Scan(&verifiedCount, &implementedCount)
+
+ if err != nil || (verifiedCount == 0 && implementedCount == 0) {
+ return ""
+ }
+ if verifiedCount > 0 {
+ return "verified"
+ }
+ return "implemented"
+}
+
+func mcTopicToIACECategory(topic string) string {
+ mapping := map[string]string{
+ "encryption": "cyber",
+ "access_control": "software",
+ "network_security": "cyber",
+ "vulnerability": "cyber",
+ "product_safety": "mechanical",
+ "physical_security": "electrical",
+ "monitoring": "software",
+ "incident": "organizational",
+ "risk_management": "general",
+ }
+ for prefix, cat := range mapping {
+ if strings.HasPrefix(topic, prefix) {
+ return cat
+ }
+ }
+ return ""
+}
+
func formatTitle(name string) string {
return strings.ReplaceAll(
strings.ReplaceAll(name, "_", " "),
diff --git a/ai-compliance-sdk/migrations/025_gap_projects.sql b/ai-compliance-sdk/migrations/025_gap_projects.sql
index 341bbb8..7596748 100644
--- a/ai-compliance-sdk/migrations/025_gap_projects.sql
+++ b/ai-compliance-sdk/migrations/025_gap_projects.sql
@@ -16,6 +16,18 @@ CREATE TABLE IF NOT EXISTS compliance.gap_projects (
processes_personal_data BOOLEAN DEFAULT false,
is_critical_infra_supplier BOOLEAN DEFAULT false,
existing_certifications JSONB DEFAULT '[]',
+ applied_norms JSONB DEFAULT '[]',
+ has_risk_assessment BOOLEAN DEFAULT false,
+ has_technical_file BOOLEAN DEFAULT false,
+ has_operating_manual BOOLEAN DEFAULT false,
+ has_sbom BOOLEAN DEFAULT false,
+ has_vuln_management BOOLEAN DEFAULT false,
+ has_update_mechanism BOOLEAN DEFAULT false,
+ has_incident_response BOOLEAN DEFAULT false,
+ has_supply_chain_mgmt BOOLEAN DEFAULT false,
+ ce_marking_since VARCHAR(20),
+ product_age VARCHAR(20),
+ iace_project_id UUID,
last_analysis_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()