feat(iace): Erweiterung 5 — Safety Knowledge Graph (React Flow)
Build + Deploy / build-admin-compliance (push) Successful in 10s
Build + Deploy / build-backend-compliance (push) Successful in 10s
Build + Deploy / build-ai-sdk (push) Successful in 9s
Build + Deploy / build-developer-portal (push) Successful in 9s
Build + Deploy / build-tts (push) Successful in 10s
Build + Deploy / build-document-crawler (push) Successful in 9s
Build + Deploy / build-dsms-gateway (push) Successful in 10s
Build + Deploy / build-dsms-node (push) Successful in 11s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / loc-budget (push) Failing after 14s
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 2m23s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Successful in 40s
CI / test-python-backend (push) Successful in 35s
CI / test-python-document-crawler (push) Successful in 25s
CI / test-python-dsms-gateway (push) Successful in 20s
CI / validate-canonical-controls (push) Successful in 14s
Build + Deploy / trigger-orca (push) Successful in 2m13s
Build + Deploy / build-admin-compliance (push) Successful in 10s
Build + Deploy / build-backend-compliance (push) Successful in 10s
Build + Deploy / build-ai-sdk (push) Successful in 9s
Build + Deploy / build-developer-portal (push) Successful in 9s
Build + Deploy / build-tts (push) Successful in 10s
Build + Deploy / build-document-crawler (push) Successful in 9s
Build + Deploy / build-dsms-gateway (push) Successful in 10s
Build + Deploy / build-dsms-node (push) Successful in 11s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / loc-budget (push) Failing after 14s
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 2m23s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Successful in 40s
CI / test-python-backend (push) Successful in 35s
CI / test-python-document-crawler (push) Successful in 25s
CI / test-python-dsms-gateway (push) Successful in 20s
CI / validate-canonical-controls (push) Successful in 14s
Build + Deploy / trigger-orca (push) Successful in 2m13s
Interaktiver Graph: Komponente → Gefaehrdung → Massnahme - 3-Spalten-Layout: Indigo (Komponenten), Rot (Hazards), Gruen (Massnahmen) - Animierte Kanten mit Pfeilmarkern - Zoom, Pan, MiniMap, Controls - Dependency: @xyflow/react v12 (MIT-Lizenz) Alle 5 IACE Phase-5 Erweiterungen jetzt abgeschlossen: 1. Betriebszustand-UI 2. FMEA-Worksheet 3. Delta-Impact-Preview Modal 4. Textil + Landmaschinen Patterns 5. Safety Knowledge Graph Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+133
@@ -0,0 +1,133 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect, useMemo } from 'react'
|
||||
|
||||
interface Component { id: string; name: string; component_type: string }
|
||||
interface Hazard { id: string; name: string; category: string; operational_states?: string[] }
|
||||
interface Mitigation { id: string; name?: string; title?: string; reduction_type: string; hazard_id?: string; linked_hazard_ids?: string[] }
|
||||
|
||||
export interface GraphNode {
|
||||
id: string
|
||||
type: 'component' | 'hazard' | 'mitigation'
|
||||
label: string
|
||||
subLabel?: string
|
||||
color: string
|
||||
}
|
||||
|
||||
export interface GraphEdge {
|
||||
id: string
|
||||
source: string
|
||||
target: string
|
||||
label?: string
|
||||
}
|
||||
|
||||
const NODE_COLORS: Record<string, string> = {
|
||||
component: '#6366F1', // indigo
|
||||
hazard: '#EF4444', // red
|
||||
mitigation: '#10B981', // green
|
||||
}
|
||||
|
||||
export function useKnowledgeGraph(projectId: string) {
|
||||
const [components, setComponents] = useState<Component[]>([])
|
||||
const [hazards, setHazards] = useState<Hazard[]>([])
|
||||
const [mitigations, setMitigations] = useState<Mitigation[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
loadData()
|
||||
}, [projectId]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
async function loadData() {
|
||||
try {
|
||||
const [compRes, hazRes, mitRes] = await Promise.all([
|
||||
fetch(`/api/sdk/v1/iace/projects/${projectId}/components`),
|
||||
fetch(`/api/sdk/v1/iace/projects/${projectId}/hazards`),
|
||||
fetch(`/api/sdk/v1/iace/projects/${projectId}/mitigations`),
|
||||
])
|
||||
|
||||
if (compRes.ok) {
|
||||
const j = await compRes.json()
|
||||
setComponents((j.components || j || []).map((c: Record<string, unknown>) => ({
|
||||
id: c.id as string, name: c.name as string, component_type: c.component_type as string || '',
|
||||
})))
|
||||
}
|
||||
if (hazRes.ok) {
|
||||
const j = await hazRes.json()
|
||||
setHazards((j.hazards || j || []).map((h: Record<string, unknown>) => ({
|
||||
id: h.id as string, name: h.name as string, category: h.category as string || '',
|
||||
operational_states: (h.operational_states || []) as string[],
|
||||
})))
|
||||
}
|
||||
if (mitRes.ok) {
|
||||
const j = await mitRes.json()
|
||||
setMitigations((j.mitigations || j || []).map((m: Record<string, unknown>) => ({
|
||||
id: m.id as string, name: (m.name || m.title || '') as string,
|
||||
title: (m.title || m.name || '') as string,
|
||||
reduction_type: (m.reduction_type || '') as string,
|
||||
hazard_id: (m.hazard_id || '') as string,
|
||||
linked_hazard_ids: (m.linked_hazard_ids || []) as string[],
|
||||
})))
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load graph data:', err)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const { nodes, edges } = useMemo(() => {
|
||||
const graphNodes: GraphNode[] = []
|
||||
const graphEdges: GraphEdge[] = []
|
||||
|
||||
// Component nodes
|
||||
components.forEach((c) => {
|
||||
graphNodes.push({
|
||||
id: `comp-${c.id}`, type: 'component',
|
||||
label: c.name, subLabel: c.component_type,
|
||||
color: NODE_COLORS.component,
|
||||
})
|
||||
})
|
||||
|
||||
// Hazard nodes
|
||||
hazards.forEach((h) => {
|
||||
graphNodes.push({
|
||||
id: `haz-${h.id}`, type: 'hazard',
|
||||
label: h.name, subLabel: h.category,
|
||||
color: NODE_COLORS.hazard,
|
||||
})
|
||||
// Edge: first component → hazard (simplified — could be per component_id)
|
||||
if (components.length > 0) {
|
||||
graphEdges.push({
|
||||
id: `e-comp-haz-${h.id}`,
|
||||
source: `comp-${components[0].id}`,
|
||||
target: `haz-${h.id}`,
|
||||
label: 'erzeugt',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Mitigation nodes
|
||||
mitigations.forEach((m) => {
|
||||
graphNodes.push({
|
||||
id: `mit-${m.id}`, type: 'mitigation',
|
||||
label: m.title || m.name || m.id,
|
||||
subLabel: m.reduction_type,
|
||||
color: NODE_COLORS.mitigation,
|
||||
})
|
||||
// Edge: mitigation → hazard
|
||||
const hazardIds = m.linked_hazard_ids?.length ? m.linked_hazard_ids : m.hazard_id ? [m.hazard_id] : []
|
||||
hazardIds.forEach((hid) => {
|
||||
graphEdges.push({
|
||||
id: `e-mit-haz-${m.id}-${hid}`,
|
||||
source: `mit-${m.id}`,
|
||||
target: `haz-${hid}`,
|
||||
label: 'schuetzt',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return { nodes: graphNodes, edges: graphEdges }
|
||||
}, [components, hazards, mitigations])
|
||||
|
||||
return { nodes, edges, loading, stats: { components: components.length, hazards: hazards.length, mitigations: mitigations.length } }
|
||||
}
|
||||
Reference in New Issue
Block a user