feat(iace): secondary-harm chain model + AllPatterns drift fix
Task #17 — Folgegefahren-Modell as Vorbereitungs-Commit (no DB schema change yet; persistence via separate [migration-approved] commit). New: - secondary_harms.go: SecondaryHarm struct + six canonical categories (consumer_safety, product_liability, food_safety, environmental, reputation, financial) with DE labels. - hazard_pattern_types.go: HazardPattern extended with optional SecondaryHarms field — pattern library can now attach consequential- damage chains. - hazard_patterns_secondary_demo.go: two worked examples - HP2000 Glasbruch carbonated bottling (the "Cola splitter" scenario from the IACE strategy discussion) with consumer_safety + food_safety + reputation chains - HP2001 Pharma fill-finish cross-contamination with consumer_safety + product_liability under AMG §84 Bonus fix: - compliance_crossover.go AllPatterns() was a duplicate enumeration that silently drifted from collectAllPatterns() in pattern_registry.go. Pre-fix: 1058 patterns visible. Post-fix: 1213 patterns. The 155 invisible patterns included CRA, ISO12100 gaps, robot-cell, CNC extended, VDMA, textile-agri, GT-bremse — anything added after the original AllPatterns was authored. Audit-Suite (cmd/iace-audit) now sees the full set. Next steps for full secondary-harm rollout: - DB migration: hazards table + secondary_harms array column - API: surface secondary_harms in /projects/:id/hazards response - Frontend: collapsible Folgegefahren-Panel in HazardTable
This commit is contained in:
@@ -104,39 +104,14 @@ func GetProjectComplianceTriggers(hazards []Hazard, patterns []HazardPattern) *C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllPatterns returns every hazard pattern from all pattern sources.
|
// AllPatterns returns every registered hazard pattern. Delegates to
|
||||||
// This mirrors the aggregation in NewPatternEngine but returns just the slice.
|
// collectAllPatterns() in pattern_registry.go so new pattern sources only
|
||||||
|
// need to be added in one place. Pre-2026-05-21 this function maintained
|
||||||
|
// a duplicate enumeration which silently drifted from the registry —
|
||||||
|
// CRA, ISO12100-gap, robot-cell, CNC, VDMA, textile-agri, GT-bremse and
|
||||||
|
// secondary-harm patterns were invisible to AllPatterns callers.
|
||||||
func AllPatterns() []HazardPattern {
|
func AllPatterns() []HazardPattern {
|
||||||
p := GetBuiltinHazardPatterns()
|
return collectAllPatterns()
|
||||||
p = append(p, GetExtendedHazardPatterns()...)
|
|
||||||
p = append(p, GetPressHazardPatterns()...)
|
|
||||||
p = append(p, GetCobotHazardPatterns()...)
|
|
||||||
p = append(p, GetOperationalHazardPatterns()...)
|
|
||||||
p = append(p, GetDGUVExtendedPatterns()...)
|
|
||||||
p = append(p, GetExtendedHazardPatterns2()...)
|
|
||||||
p = append(p, GetElevatorPatterns()...)
|
|
||||||
p = append(p, GetAGVAgriPatterns()...)
|
|
||||||
p = append(p, GetFoodProcessingPatterns()...)
|
|
||||||
p = append(p, GetPackagingPatterns()...)
|
|
||||||
p = append(p, GetLaserPatterns()...)
|
|
||||||
p = append(p, GetMedicalDevicePatterns()...)
|
|
||||||
p = append(p, GetPressureEquipmentPatterns()...)
|
|
||||||
p = append(p, GetConstructionPatterns()...)
|
|
||||||
p = append(p, GetForestryConveyorPatterns()...)
|
|
||||||
p = append(p, GetPlasticsMetalPatterns()...)
|
|
||||||
p = append(p, GetWeldingGlassTextilePatterns()...)
|
|
||||||
p = append(p, GetSpecificMachinePatterns()...)
|
|
||||||
p = append(p, GetSpecificMachinePatterns2()...)
|
|
||||||
p = append(p, GetCyberExtendedPatterns()...)
|
|
||||||
p = append(p, GetCyberExtendedPatterns2()...)
|
|
||||||
p = append(p, GetCyberExtendedPatterns3()...)
|
|
||||||
p = append(p, GetWorkshopPatterns()...)
|
|
||||||
p = append(p, GetMaintenanceExtPatterns()...)
|
|
||||||
p = append(p, GetFinalPatternsA()...)
|
|
||||||
p = append(p, GetFinalPatternsB()...)
|
|
||||||
p = append(p, GetFinalPatternsC()...)
|
|
||||||
p = append(p, GetFinalPatternsD()...)
|
|
||||||
return p
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractPatternIDs scans a text for "HP" followed by digits and adds
|
// extractPatternIDs scans a text for "HP" followed by digits and adds
|
||||||
|
|||||||
@@ -83,6 +83,12 @@ type HazardPattern struct {
|
|||||||
// feeds into the PLr (required Performance Level) computation,
|
// feeds into the PLr (required Performance Level) computation,
|
||||||
// see ComputePLr.
|
// see ComputePLr.
|
||||||
DefaultAvoidability int `json:"default_avoidability,omitempty"` // 1 or 2
|
DefaultAvoidability int `json:"default_avoidability,omitempty"` // 1 or 2
|
||||||
|
// SecondaryHarms describes consequential damage chains beyond the
|
||||||
|
// classical IACE Hazard→Harm step: end-customer safety, product
|
||||||
|
// liability, food safety, environmental, reputation, financial.
|
||||||
|
// See secondary_harms.go and the strategy discussion (2026-05-20).
|
||||||
|
// Empty for hazards with no downstream chain.
|
||||||
|
SecondaryHarms []SecondaryHarm `json:"secondary_harms,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ComputePLr returns the required Performance Level (PLr) per EN ISO
|
// ComputePLr returns the required Performance Level (PLr) per EN ISO
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
package iace
|
||||||
|
|
||||||
|
// Demonstration patterns showing how the SecondaryHarms field carries
|
||||||
|
// downstream-consequence information through the IACE engine.
|
||||||
|
//
|
||||||
|
// Two real-world scenarios are encoded:
|
||||||
|
//
|
||||||
|
// HP2000 — Glass-shard injection in carbonated-beverage bottling
|
||||||
|
// (the "Cola splitter" example from the IACE strategy
|
||||||
|
// discussion). Primary harm is the operator hit by flying
|
||||||
|
// shards; the secondary chain is product-liability towards
|
||||||
|
// supermarket end-customers.
|
||||||
|
//
|
||||||
|
// HP2001 — Cross-contamination in pharma fill-finish lines.
|
||||||
|
// Primary harm is operator exposure; secondary chain is
|
||||||
|
// patient harm + recall under §74a AMG.
|
||||||
|
//
|
||||||
|
// These two patterns are sufficient as a contract test for the
|
||||||
|
// SecondaryHarms field. Library coverage of more scenarios is a
|
||||||
|
// follow-up task once the persistence layer (DB migration) lands.
|
||||||
|
|
||||||
|
func GetSecondaryHarmDemoPatterns() []HazardPattern {
|
||||||
|
return []HazardPattern{
|
||||||
|
{
|
||||||
|
ID: "HP2000",
|
||||||
|
NameDE: "Glasbruch in Karbonisierungs-Abfueller (Hochdruck)",
|
||||||
|
NameEN: "Glass shatter in carbonated bottling line",
|
||||||
|
RequiredComponentTags: []string{"crush_point", "high_pressure"},
|
||||||
|
RequiredEnergyTags: []string{"pneumatic_pressure"},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
Priority: 90,
|
||||||
|
MachineTypes: []string{"bottling", "food_processing", "packaging"},
|
||||||
|
ScenarioDE: "Glasflasche platzt unter CO2-Druck waehrend der Abfuellung. " +
|
||||||
|
"Splitter erreichen den Bediener und koennen ferner in nachfolgende " +
|
||||||
|
"Flaschen eingetragen werden.",
|
||||||
|
TriggerDE: "Materialfehler, ueberhoehter Innendruck, Foerderstoss",
|
||||||
|
HarmDE: "Schnittverletzung Auge/Hand des Bedieners",
|
||||||
|
AffectedDE: "Abfueller, Mitarbeiter Linie",
|
||||||
|
ZoneDE: "Karussell, Schutzkapsel, Foerderband-Auslauf",
|
||||||
|
DefaultSeverity: 4,
|
||||||
|
DefaultExposure: 3,
|
||||||
|
ISO12100Section: "6.4.5.5 Schleudernde Teile",
|
||||||
|
SecondaryHarms: []SecondaryHarm{
|
||||||
|
{
|
||||||
|
Type: SecondaryHarmConsumerSafety,
|
||||||
|
Description: "Restsplitter in der Folgeflasche erreichen ueber den Handel " +
|
||||||
|
"den Endkunden. Verletzungsrisiko Mund/Speiseroehre.",
|
||||||
|
LegalBasis: "ProdHaftG §1, VO (EU) Nr. 178/2002 Art. 14",
|
||||||
|
SuggestedMitigations: []string{
|
||||||
|
"Spueltunnel nach Abfuellung",
|
||||||
|
"Inline-Kamera mit Glasbrucherkennung",
|
||||||
|
"Sperrzone fuer 2 Folgeflaschen bei Bruchereignis",
|
||||||
|
"Glasbruchsensor an Karussell mit Linie-Stopp",
|
||||||
|
},
|
||||||
|
Owner: "product_safety",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: SecondaryHarmFoodSafety,
|
||||||
|
Description: "Rueckruf- und Meldepflicht bei Inverkehrbringen unsicherer " +
|
||||||
|
"Lebensmittel; Rueckverfolgbarkeit Chargen-genau erforderlich.",
|
||||||
|
LegalBasis: "VO (EU) 178/2002 Art. 18, 19; LFGB §40",
|
||||||
|
SuggestedMitigations: []string{
|
||||||
|
"Chargen-Tracking bis Endhaendler",
|
||||||
|
"Schnellwarnsystem RASFF aktiviert halten",
|
||||||
|
"Rueckruf-SOP getestet",
|
||||||
|
},
|
||||||
|
Owner: "qm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: SecondaryHarmReputation,
|
||||||
|
Description: "Pressemitteilung und Aktienkurs-Reaktion bei Verbraucher-" +
|
||||||
|
"verletzungen / behoerdlichem Rueckruf.",
|
||||||
|
LegalBasis: "ISO 31000 Unternehmensrisiko",
|
||||||
|
SuggestedMitigations: []string{
|
||||||
|
"Krisenkommunikations-Plan",
|
||||||
|
"PR-Bereitschaft 24/7",
|
||||||
|
},
|
||||||
|
Owner: "enterprise_risk",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP2001",
|
||||||
|
NameDE: "Kreuzkontamination Pharma Fill-Finish",
|
||||||
|
NameEN: "Cross-contamination pharma fill-finish",
|
||||||
|
RequiredComponentTags: []string{"chemical_risk"},
|
||||||
|
RequiredEnergyTags: []string{"pneumatic_pressure"},
|
||||||
|
GeneratedHazardCats: []string{"chemical_hazard"},
|
||||||
|
Priority: 92,
|
||||||
|
MachineTypes: []string{"pharmaceutical", "food_processing"},
|
||||||
|
ScenarioDE: "Wirkstoff-Rueckstand aus Vorcharge im Linienzwischenraum kontaminiert " +
|
||||||
|
"die Folgecharge.",
|
||||||
|
TriggerDE: "Mangelhaftes CIP, Spuelvolumen unterhalb Validierung",
|
||||||
|
HarmDE: "Bedienerexposition bei Probennahme",
|
||||||
|
AffectedDE: "Anlagenbediener, Probenehmer",
|
||||||
|
ZoneDE: "Abfuelllinie zwischen Vorlage und Filler",
|
||||||
|
DefaultSeverity: 4,
|
||||||
|
DefaultExposure: 2,
|
||||||
|
ISO12100Section: "6.4.4 Chemische und biologische Gefaehrdungen",
|
||||||
|
SecondaryHarms: []SecondaryHarm{
|
||||||
|
{
|
||||||
|
Type: SecondaryHarmConsumerSafety,
|
||||||
|
Description: "Patient erhaelt Arzneimittel mit unzulaessiger Beimischung; " +
|
||||||
|
"Wirkungsbeeintraechtigung oder unerwuenschte Wirkung moeglich.",
|
||||||
|
LegalBasis: "AMG §5 (Verkehrsfaehigkeit), §74a (Stufenplan)",
|
||||||
|
SuggestedMitigations: []string{
|
||||||
|
"CIP-Validierung mit TOC- und Conductivity-Limits",
|
||||||
|
"Dedizierte Linien fuer Hochpotente Wirkstoffe",
|
||||||
|
"Stufenplan-Meldung bei Verdacht",
|
||||||
|
},
|
||||||
|
Owner: "qm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: SecondaryHarmProductLiability,
|
||||||
|
Description: "Haftung des Inverkehrbringers nach AMG §84 (Gefaehrdungshaftung " +
|
||||||
|
"bei Arzneimittelschaeden, verschuldensunabhaengig).",
|
||||||
|
LegalBasis: "AMG §84",
|
||||||
|
SuggestedMitigations: []string{
|
||||||
|
"Deckung Produkthaftpflicht ueber gesetzliches Minimum",
|
||||||
|
"Chargen-Rueckhaltemuster 12 Monate ueber MHD hinaus",
|
||||||
|
},
|
||||||
|
Owner: "legal",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,5 +42,6 @@ func collectAllPatterns() []HazardPattern {
|
|||||||
patterns = append(patterns, GetGTBremseHazardPatterns()...) // HP1710-HP1729 GT Bremse coverage gaps
|
patterns = append(patterns, GetGTBremseHazardPatterns()...) // HP1710-HP1729 GT Bremse coverage gaps
|
||||||
patterns = append(patterns, GetISO12100GapPatterns()...) // HP1900-HP1909 ISO 12100 Annex B gaps (Vakuum, Federn, Rutsch, Hochdruckinjektion, Ersticken)
|
patterns = append(patterns, GetISO12100GapPatterns()...) // HP1900-HP1909 ISO 12100 Annex B gaps (Vakuum, Federn, Rutsch, Hochdruckinjektion, Ersticken)
|
||||||
patterns = append(patterns, GetCRAPatterns()...) // HP1910-HP1918 CRA / DIN EN 40000-1-2 cyber-resilience spur
|
patterns = append(patterns, GetCRAPatterns()...) // HP1910-HP1918 CRA / DIN EN 40000-1-2 cyber-resilience spur
|
||||||
|
patterns = append(patterns, GetSecondaryHarmDemoPatterns()...) // HP2000-HP2001 secondary harm chain demos (Cola splitter, Pharma)
|
||||||
return patterns
|
return patterns
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package iace
|
||||||
|
|
||||||
|
// SecondaryHarm models the consequential damage chain triggered by a primary
|
||||||
|
// hazard. The classical IACE / ISO-12100 model treats Hazard -> Harm as a
|
||||||
|
// single step ("operator gets crushed"). BreakPilot extends this with a
|
||||||
|
// follow-on chain so the risk assessment can address:
|
||||||
|
//
|
||||||
|
// - consumer_safety: end customer exposed to defective product
|
||||||
|
// (e.g. glass shards in a bottled drink that reaches a supermarket)
|
||||||
|
// - product_liability: manufacturer liability under ProdHaftG / EU PLD
|
||||||
|
// - food_safety: traceability and recall obligations (VO 178/2002)
|
||||||
|
// - environmental: spill, contamination, waste-disposal consequence
|
||||||
|
// - reputation: brand damage that escalates to investor / market level
|
||||||
|
// - financial: direct cost (lawsuit, recall, fine)
|
||||||
|
//
|
||||||
|
// This struct is the data contract; persistence is deferred to a future
|
||||||
|
// migration. The pattern library can already attach SecondaryHarms to a
|
||||||
|
// HazardPattern; the API layer surfaces them on hazard generation.
|
||||||
|
//
|
||||||
|
// See memory project_attribution_strategy.md plus the "Cola splitter" worked
|
||||||
|
// example from the IACE strategy discussion (2026-05-20).
|
||||||
|
type SecondaryHarm struct {
|
||||||
|
// Type is one of the SecondaryHarmType* constants below.
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// Description is a single sentence describing the secondary harm
|
||||||
|
// scenario in concrete terms ("Splitter in Folgeflasche bei
|
||||||
|
// Karussell-Abfueller -> Endkunde verletzt").
|
||||||
|
Description string `json:"description"`
|
||||||
|
|
||||||
|
// LegalBasis cites the legal framework that turns the secondary harm
|
||||||
|
// into an actionable obligation (e.g. "ProdHaftG §1" or "VO 178/2002
|
||||||
|
// Art. 14"). Helps auditors trace the obligation.
|
||||||
|
LegalBasis string `json:"legal_basis,omitempty"`
|
||||||
|
|
||||||
|
// SuggestedMitigations is a free-text list of measures specific to
|
||||||
|
// the secondary chain (e.g. "Spueltunnel", "Inline-Kamera",
|
||||||
|
// "Glasbruchsensor"). Distinct from the primary-mitigations because
|
||||||
|
// they protect downstream stakeholders, not the operator.
|
||||||
|
SuggestedMitigations []string `json:"suggested_mitigations,omitempty"`
|
||||||
|
|
||||||
|
// Owner identifies the role responsible for handling this secondary
|
||||||
|
// harm in the customer organisation. Common values:
|
||||||
|
// "qm" / "product_safety" / "enterprise_risk" / "legal"
|
||||||
|
// Empty if responsibility is shared.
|
||||||
|
Owner string `json:"owner,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecondaryHarmType constants — kept short and stable.
|
||||||
|
const (
|
||||||
|
SecondaryHarmConsumerSafety = "consumer_safety"
|
||||||
|
SecondaryHarmProductLiability = "product_liability"
|
||||||
|
SecondaryHarmFoodSafety = "food_safety"
|
||||||
|
SecondaryHarmEnvironmental = "environmental"
|
||||||
|
SecondaryHarmReputation = "reputation"
|
||||||
|
SecondaryHarmFinancial = "financial"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AllSecondaryHarmTypes returns the canonical six categories in the order
|
||||||
|
// they should appear in UI dropdowns.
|
||||||
|
func AllSecondaryHarmTypes() []string {
|
||||||
|
return []string{
|
||||||
|
SecondaryHarmConsumerSafety,
|
||||||
|
SecondaryHarmProductLiability,
|
||||||
|
SecondaryHarmFoodSafety,
|
||||||
|
SecondaryHarmEnvironmental,
|
||||||
|
SecondaryHarmReputation,
|
||||||
|
SecondaryHarmFinancial,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecondaryHarmLabelDE returns the human-readable German label.
|
||||||
|
func SecondaryHarmLabelDE(t string) string {
|
||||||
|
switch t {
|
||||||
|
case SecondaryHarmConsumerSafety:
|
||||||
|
return "Endkundensicherheit"
|
||||||
|
case SecondaryHarmProductLiability:
|
||||||
|
return "Produkthaftung"
|
||||||
|
case SecondaryHarmFoodSafety:
|
||||||
|
return "Lebensmittelsicherheit"
|
||||||
|
case SecondaryHarmEnvironmental:
|
||||||
|
return "Umweltschaden"
|
||||||
|
case SecondaryHarmReputation:
|
||||||
|
return "Reputation/Marke"
|
||||||
|
case SecondaryHarmFinancial:
|
||||||
|
return "Finanzieller Schaden"
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user