9a9a11b248
Neuer Endpoint POST /projects/:id/delta-analysis: - Input: aktuelle + vorgeschlagene Aenderung (Components, Energy, States, Roles) - Output: Diff der Pattern-Matches (added/removed Patterns, Hazards, Measures) - DeltaMatch() auf PatternEngine: Match(current) vs Match(proposed) - DeltaResult mit AddedPatterns, RemovedPatterns, Counts, SummaryDE Beispiel-Output: SPS hinzufuegen → +55 Patterns, +5 Hazard-Kategorien, +17 Massnahmen Maintenance-State hinzufuegen → +10 Patterns, +2 Hazards, +2 Massnahmen 7 Tests: NoChange, AddComponent, RemoveComponent, AddState, AddRole, Summary, Symmetric Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
139 lines
4.8 KiB
Go
139 lines
4.8 KiB
Go
package iace
|
|
|
|
import "testing"
|
|
|
|
func TestDeltaMatch_NoChange(t *testing.T) {
|
|
engine := NewPatternEngine()
|
|
input := MatchInput{
|
|
ComponentLibraryIDs: []string{"C001"},
|
|
EnergySourceIDs: []string{"EN01"},
|
|
}
|
|
result := engine.DeltaMatch(input, input)
|
|
|
|
if len(result.AddedPatterns) != 0 {
|
|
t.Errorf("expected 0 added patterns for no change, got %d", len(result.AddedPatterns))
|
|
}
|
|
if len(result.RemovedPatterns) != 0 {
|
|
t.Errorf("expected 0 removed patterns for no change, got %d", len(result.RemovedPatterns))
|
|
}
|
|
if result.SummaryDE != "Keine Auswirkung — die vorgeschlagene Aenderung veraendert die Risikobeurteilung nicht." {
|
|
t.Errorf("unexpected summary: %s", result.SummaryDE)
|
|
}
|
|
}
|
|
|
|
func TestDeltaMatch_AddComponent(t *testing.T) {
|
|
engine := NewPatternEngine()
|
|
current := MatchInput{
|
|
ComponentLibraryIDs: []string{"C001"},
|
|
EnergySourceIDs: []string{"EN01"},
|
|
}
|
|
proposed := MatchInput{
|
|
ComponentLibraryIDs: []string{"C001", "C071"}, // Add SPS (programmable)
|
|
EnergySourceIDs: []string{"EN01"},
|
|
}
|
|
result := engine.DeltaMatch(current, proposed)
|
|
|
|
if len(result.AddedPatterns) == 0 {
|
|
t.Error("expected added patterns when adding SPS component")
|
|
}
|
|
t.Logf("Adding C071 (SPS): +%d patterns, +%d hazard cats, +%d measures",
|
|
len(result.AddedPatterns), result.AddedHazardCount, result.AddedMeasureCount)
|
|
}
|
|
|
|
func TestDeltaMatch_RemoveComponent(t *testing.T) {
|
|
engine := NewPatternEngine()
|
|
current := MatchInput{
|
|
ComponentLibraryIDs: []string{"C001", "C071"},
|
|
EnergySourceIDs: []string{"EN01"},
|
|
}
|
|
proposed := MatchInput{
|
|
ComponentLibraryIDs: []string{"C001"}, // Remove SPS
|
|
EnergySourceIDs: []string{"EN01"},
|
|
}
|
|
result := engine.DeltaMatch(current, proposed)
|
|
|
|
if len(result.RemovedPatterns) == 0 {
|
|
t.Error("expected removed patterns when removing SPS component")
|
|
}
|
|
t.Logf("Removing C071: -%d patterns, -%d hazard cats",
|
|
len(result.RemovedPatterns), result.RemovedHazardCount)
|
|
}
|
|
|
|
func TestDeltaMatch_AddState(t *testing.T) {
|
|
engine := NewPatternEngine()
|
|
current := MatchInput{
|
|
ComponentLibraryIDs: []string{"C001", "C071"},
|
|
EnergySourceIDs: []string{"EN01"},
|
|
LifecyclePhases: []string{"normal_operation"},
|
|
OperationalStates: []string{"automatic_operation"},
|
|
HumanRoles: []string{"operator"},
|
|
}
|
|
proposed := MatchInput{
|
|
ComponentLibraryIDs: []string{"C001", "C071"},
|
|
EnergySourceIDs: []string{"EN01"},
|
|
LifecyclePhases: []string{"normal_operation", "maintenance"},
|
|
OperationalStates: []string{"automatic_operation", "maintenance"},
|
|
HumanRoles: []string{"operator", "maintenance_tech"},
|
|
}
|
|
result := engine.DeltaMatch(current, proposed)
|
|
|
|
t.Logf("Adding maintenance state+role: +%d patterns, +%d hazards, +%d measures",
|
|
len(result.AddedPatterns), result.AddedHazardCount, result.AddedMeasureCount)
|
|
|
|
// Adding maintenance should bring maintenance-specific patterns
|
|
if len(result.AddedPatterns) == 0 {
|
|
t.Error("expected added patterns when adding maintenance state")
|
|
}
|
|
}
|
|
|
|
func TestDeltaMatch_AddRole(t *testing.T) {
|
|
engine := NewPatternEngine()
|
|
current := MatchInput{
|
|
ComponentLibraryIDs: []string{"C001", "C071"},
|
|
EnergySourceIDs: []string{"EN01"},
|
|
LifecyclePhases: []string{"maintenance"},
|
|
OperationalStates: []string{"maintenance"},
|
|
HumanRoles: []string{"operator"}, // Only operator
|
|
}
|
|
proposed := MatchInput{
|
|
ComponentLibraryIDs: []string{"C001", "C071"},
|
|
EnergySourceIDs: []string{"EN01"},
|
|
LifecyclePhases: []string{"maintenance"},
|
|
OperationalStates: []string{"maintenance"},
|
|
HumanRoles: []string{"operator", "maintenance_tech"}, // Add maintenance_tech
|
|
}
|
|
result := engine.DeltaMatch(current, proposed)
|
|
|
|
t.Logf("Adding maintenance_tech role: +%d patterns", len(result.AddedPatterns))
|
|
// maintenance_tech role should unlock maintenance-specific patterns
|
|
if len(result.AddedPatterns) == 0 {
|
|
t.Error("expected added patterns when adding maintenance_tech role")
|
|
}
|
|
}
|
|
|
|
func TestDeltaMatch_SummaryNotEmpty(t *testing.T) {
|
|
engine := NewPatternEngine()
|
|
current := MatchInput{ComponentLibraryIDs: []string{"C001"}, EnergySourceIDs: []string{"EN01"}}
|
|
proposed := MatchInput{ComponentLibraryIDs: []string{"C001", "C071"}, EnergySourceIDs: []string{"EN01"}}
|
|
|
|
result := engine.DeltaMatch(current, proposed)
|
|
if result.SummaryDE == "" {
|
|
t.Error("expected non-empty summary")
|
|
}
|
|
t.Logf("Summary: %s", result.SummaryDE)
|
|
}
|
|
|
|
func TestDeltaMatch_Symmetric(t *testing.T) {
|
|
engine := NewPatternEngine()
|
|
a := MatchInput{ComponentLibraryIDs: []string{"C001"}, EnergySourceIDs: []string{"EN01"}}
|
|
b := MatchInput{ComponentLibraryIDs: []string{"C001", "C071"}, EnergySourceIDs: []string{"EN01"}}
|
|
|
|
forward := engine.DeltaMatch(a, b)
|
|
backward := engine.DeltaMatch(b, a)
|
|
|
|
if len(forward.AddedPatterns) != len(backward.RemovedPatterns) {
|
|
t.Errorf("forward added (%d) should equal backward removed (%d)",
|
|
len(forward.AddedPatterns), len(backward.RemovedPatterns))
|
|
}
|
|
}
|