feat(sdk): add global seq numbering and visibleWhen for SDK flow navigation

Fix interleaved step ordering by introducing global sequence numbers (100-4700)
instead of package-relative order. Add conditional visibility (visibleWhen) for
optional steps like Import and DSFA. Fix TOM/workflow prerequisite bugs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Boenisch
2026-02-25 22:26:40 +01:00
parent 16e3c251cc
commit 3efa391de5
4 changed files with 272 additions and 207 deletions

View File

@@ -264,13 +264,11 @@ function SidebarContent({ onNavigate }: SidebarContentProps) {
return packageCompletion[prevPkg.id] < 100
}
// Get visible steps based on customer type
const getVisibleSteps = (packageId: SDKPackageId): SDKStep[] => {
// Filter steps based on visibleWhen conditions
const getVisibleStepsForPackage = (packageId: SDKPackageId): SDKStep[] => {
const steps = getStepsForPackage(packageId)
return steps.filter(step => {
if (step.id === 'import' && state.customerType === 'new') {
return false
}
if (step.visibleWhen) return step.visibleWhen(state)
return true
})
}
@@ -282,7 +280,7 @@ function SidebarContent({ onNavigate }: SidebarContentProps) {
<PackageSection
key={pkg.id}
pkg={pkg}
steps={getVisibleSteps(pkg.id)}
steps={getVisibleStepsForPackage(pkg.id)}
completion={packageCompletion[pkg.id]}
currentStepId={currentStepId}
completedSteps={state.completedSteps}
@@ -296,43 +294,6 @@ function SidebarContent({ onNavigate }: SidebarContentProps) {
{/* Pipeline Flow */}
<PipelineFlow />
{/* Zusatzmodule */}
<div className="pt-3 border-t border-slate-200 dark:border-gray-700">
<div className="text-xs font-medium text-slate-500 dark:text-slate-400 mb-2 px-1">
Zusatzmodule
</div>
<div className="space-y-1">
{[
{ href: '/sdk/rag', label: 'Legal RAG', icon: '🔍' },
{ href: '/sdk/quality', label: 'AI Quality', icon: '✅' },
{ href: '/sdk/security-backlog', label: 'Security Backlog', icon: '⚠️' },
{ href: '/sdk/compliance-hub', label: 'Compliance Hub', icon: '📊' },
{ href: '/sdk/dsms', label: 'DSMS', icon: '🛡️' },
{ href: '/sdk/academy', label: 'Academy', icon: '🎓' },
{ href: '/sdk/whistleblower', label: 'Whistleblower', icon: '📢' },
{ href: '/sdk/incidents', label: 'Incidents', icon: '🚨' },
{ href: '/sdk/reporting', label: 'Reporting', icon: '📈' },
{ href: '/sdk/industry-templates', label: 'Branchenvorlagen', icon: '🏢' },
{ href: '/sdk/document-crawler', label: 'Doc Crawler', icon: '📄' },
{ href: '/sdk/advisory-board', label: 'Beirat', icon: '💬' },
].map(mod => (
<Link
key={mod.href}
href={mod.href}
onClick={onNavigate}
className={`flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm transition-colors ${
pathname === mod.href
? 'bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 font-medium'
: 'text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-gray-800'
}`}
>
<span className="text-base">{mod.icon}</span>
<span className="truncate">{mod.label}</span>
</Link>
))}
</div>
</div>
{/* Quick Info */}
{currentStep && (
<div className="pt-3 border-t border-slate-200 dark:border-gray-700">

View File

@@ -347,14 +347,11 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
return steps.some(s => s.url === pathname)
}
// Filter steps based on customer type
const getVisibleSteps = (packageId: SDKPackageId): SDKStep[] => {
// Filter steps based on visibleWhen conditions
const getVisibleStepsForPackage = (packageId: SDKPackageId): SDKStep[] => {
const steps = getStepsForPackage(packageId)
return steps.filter(step => {
// Hide import step for new customers
if (step.id === 'import' && state.customerType === 'new') {
return false
}
if (step.visibleWhen) return step.visibleWhen(state)
return true
})
}
@@ -397,7 +394,7 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
{/* Navigation - 5 Packages */}
<nav className="flex-1 overflow-y-auto">
{SDK_PACKAGES.map(pkg => {
const steps = getVisibleSteps(pkg.id)
const steps = getVisibleStepsForPackage(pkg.id)
const isLocked = isPackageLocked(pkg.id)
const isActive = isPackageActive(pkg.id)
@@ -513,116 +510,20 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
isActive={pathname === '/sdk/dsms'}
collapsed={collapsed}
/>
<AdditionalModuleItem
href="/sdk/academy"
icon={
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M12 14l9-5-9-5-9 5 9 5zm0 0l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14zm-4 6v-7.5l4-2.222" />
</svg>
}
label="Academy"
isActive={pathname === '/sdk/academy'}
collapsed={collapsed}
/>
<AdditionalModuleItem
href="/sdk/training"
icon={
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
</svg>
}
label="Training"
isActive={pathname === '/sdk/training'}
collapsed={collapsed}
/>
<AdditionalModuleItem
href="/sdk/whistleblower"
icon={
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M11 5.882V19.24a1.76 1.76 0 01-3.417.592l-2.147-6.15M18 13a3 3 0 100-6M5.436 13.683A4.001 4.001 0 017 6h1.832c4.1 0 7.625-1.234 9.168-3v14c-1.543-1.766-5.067-3-9.168-3H7a3.988 3.988 0 01-1.564-.317z" />
</svg>
}
label="Whistleblower"
isActive={pathname === '/sdk/whistleblower'}
collapsed={collapsed}
/>
<AdditionalModuleItem
href="/sdk/incidents"
icon={
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
}
label="Incidents"
isActive={pathname === '/sdk/incidents'}
collapsed={collapsed}
/>
<AdditionalModuleItem
href="/sdk/reporting"
icon={
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
}
label="Reporting"
isActive={pathname === '/sdk/reporting'}
collapsed={collapsed}
/>
<AdditionalModuleItem
href="/sdk/gci"
icon={
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M11 3.055A9.001 9.001 0 1020.945 13H11V3.055z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M20.488 9H15V3.512A9.025 9.025 0 0120.488 9z" />
</svg>
}
label="GCI Score"
isActive={pathname === '/sdk/gci'}
collapsed={collapsed}
/>
<AdditionalModuleItem
href="/sdk/industry-templates"
icon={
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
}
label="Branchenvorlagen"
isActive={pathname === '/sdk/industry-templates'}
collapsed={collapsed}
/>
<AdditionalModuleItem
href="/sdk/document-crawler"
icon={
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M10 21h7a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v11m0 5l4.879-4.879m0 0a3 3 0 104.243-4.242 3 3 0 00-4.243 4.242z" />
</svg>
}
label="Doc Crawler"
isActive={pathname === '/sdk/document-crawler'}
collapsed={collapsed}
/>
<AdditionalModuleItem
href="/sdk/advisory-board"
icon={
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
}
label="Beirat"
isActive={pathname === '/sdk/advisory-board'}
collapsed={collapsed}
/>
{state.companyProfile?.machineBuilder?.ceMarkingRequired && (
<AdditionalModuleItem
href="/sdk/iace"
icon={
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
</svg>
}
label="CE-Compliance (IACE)"
isActive={pathname?.startsWith('/sdk/iace') ?? false}
collapsed={collapsed}
/>
)}
</div>
</nav>

View File

@@ -99,6 +99,9 @@ const initialState: SDKState = {
dsrConfig: null,
escalationWorkflows: [],
// IACE (Industrial AI Compliance Engine)
iaceProjects: [],
// Security
sbom: null,
securityIssues: [],
@@ -705,26 +708,26 @@ export function SDKProvider({
)
const goToNextStep = useCallback(() => {
const nextStep = getNextStep(state.currentStep)
const nextStep = getNextStep(state.currentStep, state)
if (nextStep) {
goToStep(nextStep.id)
}
}, [state.currentStep, goToStep])
}, [state, goToStep])
const goToPreviousStep = useCallback(() => {
const prevStep = getPreviousStep(state.currentStep)
const prevStep = getPreviousStep(state.currentStep, state)
if (prevStep) {
goToStep(prevStep.id)
}
}, [state.currentStep, goToStep])
}, [state, goToStep])
const canGoNext = useMemo(() => {
return getNextStep(state.currentStep) !== undefined
}, [state.currentStep])
return getNextStep(state.currentStep, state) !== undefined
}, [state])
const canGoPrevious = useMemo(() => {
return getPreviousStep(state.currentStep) !== undefined
}, [state.currentStep])
return getPreviousStep(state.currentStep, state) !== undefined
}, [state])
// Progress
const completionPercentage = useMemo(() => getCompletionPercentage(state), [state])

View File

@@ -62,6 +62,86 @@ export type LegalForm =
| 'stiftung' // Foundation
| 'other' // Other
// =============================================================================
// MACHINE BUILDER PROFILE (IACE - Industrial AI Compliance Engine)
// =============================================================================
export type MachineProductType = 'test_stand' | 'robot_cell' | 'special_machine' | 'production_line' | 'other'
export type AIIntegrationType = 'vision' | 'predictive_maintenance' | 'quality_control' | 'robot_control' | 'process_optimization' | 'other'
export type HumanOversightLevel = 'full' | 'partial' | 'minimal' | 'none'
export type CriticalSector = 'energy' | 'water' | 'transport' | 'health' | 'pharma' | 'automotive' | 'defense'
export interface MachineBuilderProfile {
// Produkt
productTypes: MachineProductType[]
productDescription: string
productPride: string
containsSoftware: boolean
containsFirmware: boolean
containsAI: boolean
aiIntegrationType: AIIntegrationType[]
// Sicherheit
hasSafetyFunction: boolean
safetyFunctionDescription: string
autonomousBehavior: boolean
humanOversightLevel: HumanOversightLevel
// Konnektivitaet
isNetworked: boolean
hasRemoteAccess: boolean
hasOTAUpdates: boolean
updateMechanism: string
// Markt & Kunden
exportMarkets: string[]
criticalSectorClients: boolean
criticalSectors: CriticalSector[]
oemClients: boolean
// CE
ceMarkingRequired: boolean
existingCEProcess: boolean
hasRiskAssessment: boolean
}
export const MACHINE_PRODUCT_TYPE_LABELS: Record<MachineProductType, string> = {
test_stand: 'Pruefstand',
robot_cell: 'Roboterzelle',
special_machine: 'Sondermaschine',
production_line: 'Produktionslinie',
other: 'Sonstige',
}
export const AI_INTEGRATION_TYPE_LABELS: Record<AIIntegrationType, string> = {
vision: 'Bildverarbeitung / Machine Vision',
predictive_maintenance: 'Predictive Maintenance',
quality_control: 'Qualitaetskontrolle',
robot_control: 'Robotersteuerung',
process_optimization: 'Prozessoptimierung',
other: 'Sonstige',
}
export const HUMAN_OVERSIGHT_LABELS: Record<HumanOversightLevel, string> = {
full: 'Vollstaendig (Mensch entscheidet immer)',
partial: 'Teilweise (Mensch ueberwacht)',
minimal: 'Minimal (Mensch greift nur bei Stoerung ein)',
none: 'Keine (vollautonomer Betrieb)',
}
export const CRITICAL_SECTOR_LABELS: Record<CriticalSector, string> = {
energy: 'Energie',
water: 'Wasser',
transport: 'Transport / Verkehr',
health: 'Gesundheit',
pharma: 'Pharma',
automotive: 'Automotive',
defense: 'Verteidigung',
}
export interface CompanyProfile {
// Basic Info
companyName: string
@@ -102,6 +182,9 @@ export interface CompanyProfile {
legalContactName: string | null
legalContactEmail: string | null
// Machine Builder (IACE)
machineBuilder?: MachineBuilderProfile
// Completion Status
isComplete: boolean
completedAt: Date | null
@@ -284,6 +367,7 @@ export type CommandType = 'ACTION' | 'NAVIGATION' | 'SEARCH' | 'GENERATE' | 'HEL
export interface SDKStep {
id: string
seq: number // Globale Sequenznummer (100, 200, 300, ...)
phase: SDKPhase
package: SDKPackageId
order: number
@@ -294,6 +378,7 @@ export interface SDKStep {
checkpointId: string
prerequisiteSteps: string[]
isOptional: boolean
visibleWhen?: (state: SDKState) => boolean // Konditionale Sichtbarkeit
}
export const SDK_STEPS: SDKStep[] = [
@@ -302,6 +387,7 @@ export const SDK_STEPS: SDKStep[] = [
// =============================================================================
{
id: 'company-profile',
seq: 100,
phase: 1,
package: 'vorbereitung',
order: 1,
@@ -315,6 +401,7 @@ export const SDK_STEPS: SDKStep[] = [
},
{
id: 'compliance-scope',
seq: 200,
phase: 1,
package: 'vorbereitung',
order: 2,
@@ -328,6 +415,7 @@ export const SDK_STEPS: SDKStep[] = [
},
{
id: 'use-case-assessment',
seq: 300,
phase: 1,
package: 'vorbereitung',
order: 3,
@@ -341,6 +429,7 @@ export const SDK_STEPS: SDKStep[] = [
},
{
id: 'import',
seq: 400,
phase: 1,
package: 'vorbereitung',
order: 4,
@@ -350,10 +439,12 @@ export const SDK_STEPS: SDKStep[] = [
url: '/sdk/import',
checkpointId: 'CP-IMP',
prerequisiteSteps: ['use-case-assessment'],
isOptional: true, // Nur für Bestandskunden
isOptional: true,
visibleWhen: (state) => state.customerType === 'existing',
},
{
id: 'screening',
seq: 500,
phase: 1,
package: 'vorbereitung',
order: 5,
@@ -367,6 +458,7 @@ export const SDK_STEPS: SDKStep[] = [
},
{
id: 'modules',
seq: 600,
phase: 1,
package: 'vorbereitung',
order: 6,
@@ -380,6 +472,7 @@ export const SDK_STEPS: SDKStep[] = [
},
{
id: 'source-policy',
seq: 700,
phase: 1,
package: 'vorbereitung',
order: 7,
@@ -397,6 +490,7 @@ export const SDK_STEPS: SDKStep[] = [
// =============================================================================
{
id: 'requirements',
seq: 1000,
phase: 1,
package: 'analyse',
order: 1,
@@ -410,6 +504,7 @@ export const SDK_STEPS: SDKStep[] = [
},
{
id: 'controls',
seq: 1100,
phase: 1,
package: 'analyse',
order: 2,
@@ -423,6 +518,7 @@ export const SDK_STEPS: SDKStep[] = [
},
{
id: 'evidence',
seq: 1200,
phase: 1,
package: 'analyse',
order: 3,
@@ -436,6 +532,7 @@ export const SDK_STEPS: SDKStep[] = [
},
{
id: 'risks',
seq: 1300,
phase: 1,
package: 'analyse',
order: 4,
@@ -449,6 +546,7 @@ export const SDK_STEPS: SDKStep[] = [
},
{
id: 'ai-act',
seq: 1400,
phase: 1,
package: 'analyse',
order: 5,
@@ -462,6 +560,7 @@ export const SDK_STEPS: SDKStep[] = [
},
{
id: 'audit-checklist',
seq: 1500,
phase: 1,
package: 'analyse',
order: 6,
@@ -475,6 +574,7 @@ export const SDK_STEPS: SDKStep[] = [
},
{
id: 'audit-report',
seq: 1600,
phase: 1,
package: 'analyse',
order: 7,
@@ -492,6 +592,7 @@ export const SDK_STEPS: SDKStep[] = [
// =============================================================================
{
id: 'obligations',
seq: 2000,
phase: 2,
package: 'dokumentation',
order: 1,
@@ -505,6 +606,7 @@ export const SDK_STEPS: SDKStep[] = [
},
{
id: 'dsfa',
seq: 2100,
phase: 2,
package: 'dokumentation',
order: 2,
@@ -514,10 +616,17 @@ export const SDK_STEPS: SDKStep[] = [
url: '/sdk/dsfa',
checkpointId: 'CP-DSFA',
prerequisiteSteps: ['obligations'],
isOptional: true, // Only if dsfa_recommended
isOptional: true,
visibleWhen: (state) => {
const level = state.complianceScope?.decision?.determinedLevel
if (level && ['L2', 'L3', 'L4'].includes(level)) return true
const triggers = state.complianceScope?.decision?.triggeredHardTriggers || []
return triggers.some(t => t.rule.dsfaRequired)
},
},
{
id: 'tom',
seq: 2200,
phase: 2,
package: 'dokumentation',
order: 3,
@@ -526,11 +635,12 @@ export const SDK_STEPS: SDKStep[] = [
description: 'Technische & Org. Maßnahmen',
url: '/sdk/tom',
checkpointId: 'CP-TOM',
prerequisiteSteps: ['dsfa'],
prerequisiteSteps: ['obligations'],
isOptional: false,
},
{
id: 'loeschfristen',
seq: 2300,
phase: 2,
package: 'dokumentation',
order: 4,
@@ -544,6 +654,7 @@ export const SDK_STEPS: SDKStep[] = [
},
{
id: 'vvt',
seq: 2400,
phase: 2,
package: 'dokumentation',
order: 5,
@@ -561,6 +672,7 @@ export const SDK_STEPS: SDKStep[] = [
// =============================================================================
{
id: 'einwilligungen',
seq: 3000,
phase: 2,
package: 'rechtliche-texte',
order: 1,
@@ -574,6 +686,7 @@ export const SDK_STEPS: SDKStep[] = [
},
{
id: 'consent',
seq: 3100,
phase: 2,
package: 'rechtliche-texte',
order: 2,
@@ -587,6 +700,7 @@ export const SDK_STEPS: SDKStep[] = [
},
{
id: 'cookie-banner',
seq: 3200,
phase: 2,
package: 'rechtliche-texte',
order: 3,
@@ -600,6 +714,7 @@ export const SDK_STEPS: SDKStep[] = [
},
{
id: 'document-generator',
seq: 3300,
phase: 2,
package: 'rechtliche-texte',
order: 4,
@@ -610,9 +725,11 @@ export const SDK_STEPS: SDKStep[] = [
checkpointId: 'CP-DOCGEN',
prerequisiteSteps: ['cookie-banner'],
isOptional: true,
visibleWhen: () => true,
},
{
id: 'workflow',
seq: 3400,
phase: 2,
package: 'rechtliche-texte',
order: 5,
@@ -621,7 +738,7 @@ export const SDK_STEPS: SDKStep[] = [
description: 'Versionierung & Freigabe-Workflow',
url: '/sdk/workflow',
checkpointId: 'CP-WRKF',
prerequisiteSteps: ['document-generator'],
prerequisiteSteps: ['cookie-banner'],
isOptional: false,
},
@@ -630,6 +747,7 @@ export const SDK_STEPS: SDKStep[] = [
// =============================================================================
{
id: 'dsr',
seq: 4000,
phase: 2,
package: 'betrieb',
order: 1,
@@ -643,6 +761,7 @@ export const SDK_STEPS: SDKStep[] = [
},
{
id: 'escalations',
seq: 4100,
phase: 2,
package: 'betrieb',
order: 2,
@@ -656,6 +775,7 @@ export const SDK_STEPS: SDKStep[] = [
},
{
id: 'vendor-compliance',
seq: 4200,
phase: 2,
package: 'betrieb',
order: 3,
@@ -669,6 +789,7 @@ export const SDK_STEPS: SDKStep[] = [
},
{
id: 'consent-management',
seq: 4300,
phase: 2,
package: 'betrieb',
order: 4,
@@ -682,6 +803,7 @@ export const SDK_STEPS: SDKStep[] = [
},
{
id: 'notfallplan',
seq: 4400,
phase: 2,
package: 'betrieb',
order: 5,
@@ -693,6 +815,48 @@ export const SDK_STEPS: SDKStep[] = [
prerequisiteSteps: ['consent-management'],
isOptional: false,
},
{
id: 'incidents',
seq: 4500,
phase: 2,
package: 'betrieb',
order: 6,
name: 'Incident Management',
nameShort: 'Incidents',
description: 'Datenpannen erfassen, bewerten und melden (Art. 33/34 DSGVO)',
url: '/sdk/incidents',
checkpointId: 'CP-INC',
prerequisiteSteps: ['notfallplan'],
isOptional: false,
},
{
id: 'whistleblower',
seq: 4600,
phase: 2,
package: 'betrieb',
order: 7,
name: 'Hinweisgebersystem',
nameShort: 'Whistleblower',
description: 'Anonymes Meldesystem gemaess HinSchG',
url: '/sdk/whistleblower',
checkpointId: 'CP-WB',
prerequisiteSteps: ['incidents'],
isOptional: false,
},
{
id: 'academy',
seq: 4700,
phase: 2,
package: 'betrieb',
order: 8,
name: 'Compliance Academy',
nameShort: 'Academy',
description: 'Mitarbeiter-Schulungen & Zertifikate',
url: '/sdk/academy',
checkpointId: 'CP-ACAD',
prerequisiteSteps: ['whistleblower'],
isOptional: false,
},
]
// =============================================================================
@@ -1323,6 +1487,9 @@ export interface SDKState {
dsrConfig: DSRConfig | null
escalationWorkflows: EscalationWorkflow[]
// IACE (Industrial AI Compliance Engine)
iaceProjects: IACEProjectSummary[]
// Security
sbom: SBOM | null
securityIssues: SecurityIssue[]
@@ -1334,6 +1501,28 @@ export interface SDKState {
preferences: UserPreferences
}
// =============================================================================
// IACE PROJECT TYPES
// =============================================================================
export type IACEProjectStatus = 'draft' | 'onboarding' | 'classification' | 'hazard_analysis' | 'mitigation' | 'verification' | 'tech_file' | 'completed' | 'archived'
export interface IACEProjectSummary {
id: string
machineName: string
machineType: MachineProductType
status: IACEProjectStatus
completenessScore: number
riskSummary: {
critical: number
high: number
medium: number
low: number
}
createdAt: string
updatedAt: string
}
// =============================================================================
// SDK ACTIONS
// =============================================================================
@@ -1406,48 +1595,59 @@ export function getStepByUrl(url: string): SDKStep | undefined {
}
export function getStepsForPhase(phase: SDKPhase): SDKStep[] {
return SDK_STEPS.filter(s => s.phase === phase).sort((a, b) => a.order - b.order)
return SDK_STEPS.filter(s => s.phase === phase).sort((a, b) => a.seq - b.seq)
}
export function getNextStep(currentStepId: string): SDKStep | undefined {
const currentStep = getStepById(currentStepId)
if (!currentStep) return undefined
// Alle Steps global nach seq sortiert
function getAllStepsSorted(): SDKStep[] {
return [...SDK_STEPS].sort((a, b) => a.seq - b.seq)
}
const stepsInPhase = getStepsForPhase(currentStep.phase)
const currentIndex = stepsInPhase.findIndex(s => s.id === currentStepId)
if (currentIndex < stepsInPhase.length - 1) {
return stepsInPhase[currentIndex + 1]
}
// Move to next phase
if (currentStep.phase === 1) {
return getStepsForPhase(2)[0]
}
// Sichtbare Steps (state-abhaengig)
export function getVisibleSteps(state: SDKState): SDKStep[] {
return getAllStepsSorted().filter(step => {
if (step.visibleWhen) return step.visibleWhen(state)
return true
})
}
// Naechster sichtbarer Step
export function getNextVisibleStep(currentStepId: string, state: SDKState): SDKStep | undefined {
const visible = getVisibleSteps(state)
const idx = visible.findIndex(s => s.id === currentStepId)
if (idx >= 0 && idx < visible.length - 1) return visible[idx + 1]
return undefined
}
export function getPreviousStep(currentStepId: string): SDKStep | undefined {
const currentStep = getStepById(currentStepId)
if (!currentStep) return undefined
const stepsInPhase = getStepsForPhase(currentStep.phase)
const currentIndex = stepsInPhase.findIndex(s => s.id === currentStepId)
if (currentIndex > 0) {
return stepsInPhase[currentIndex - 1]
}
// Move to previous phase
if (currentStep.phase === 2) {
const phase1Steps = getStepsForPhase(1)
return phase1Steps[phase1Steps.length - 1]
}
// Vorheriger sichtbarer Step
export function getPreviousVisibleStep(currentStepId: string, state: SDKState): SDKStep | undefined {
const visible = getVisibleSteps(state)
const idx = visible.findIndex(s => s.id === currentStepId)
if (idx > 0) return visible[idx - 1]
return undefined
}
export function getNextStep(currentStepId: string, state?: SDKState): SDKStep | undefined {
if (!state) {
// Fallback: seq-sortiert ohne Sichtbarkeitspruefung
const sorted = getAllStepsSorted()
const idx = sorted.findIndex(s => s.id === currentStepId)
if (idx >= 0 && idx < sorted.length - 1) return sorted[idx + 1]
return undefined
}
return getNextVisibleStep(currentStepId, state)
}
export function getPreviousStep(currentStepId: string, state?: SDKState): SDKStep | undefined {
if (!state) {
const sorted = getAllStepsSorted()
const idx = sorted.findIndex(s => s.id === currentStepId)
if (idx > 0) return sorted[idx - 1]
return undefined
}
return getPreviousVisibleStep(currentStepId, state)
}
export function calculateRiskScore(likelihood: RiskLikelihood, impact: RiskImpact): number {
return likelihood * impact
}
@@ -1490,7 +1690,7 @@ export function getPackageById(packageId: SDKPackageId): SDKPackage | undefined
}
export function getStepsForPackage(packageId: SDKPackageId): SDKStep[] {
return SDK_STEPS.filter(s => s.package === packageId).sort((a, b) => a.order - b.order)
return SDK_STEPS.filter(s => s.package === packageId).sort((a, b) => a.seq - b.seq)
}
export function getPackageCompletionPercentage(state: SDKState, packageId: SDKPackageId): number {
@@ -1545,9 +1745,9 @@ export function isPackageUnlocked(state: SDKState, packageId: SDKPackageId): boo
return getPackageCompletionPercentage(state, prevPackage.id) === 100
}
/** @deprecated Use getVisibleSteps(state) instead */
export function getVisibleStepsForCustomerType(customerType: CustomerType): SDKStep[] {
return SDK_STEPS.filter(step => {
// Import step is only for existing customers
return getAllStepsSorted().filter(step => {
if (step.id === 'import') {
return customerType === 'existing'
}