feat(iace): per-category hazard caps for precision improvement
Build + Deploy / build-dsms-node (push) Successful in 11s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
Build + Deploy / build-admin-compliance (push) Successful in 12s
Build + Deploy / build-backend-compliance (push) Successful in 11s
Build + Deploy / build-ai-sdk (push) Successful in 40s
Build + Deploy / build-developer-portal (push) Successful in 10s
Build + Deploy / build-tts (push) Successful in 10s
Build + Deploy / build-document-crawler (push) Successful in 10s
Build + Deploy / build-dsms-gateway (push) Successful in 10s
CI / loc-budget (push) Failing after 13s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m33s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Successful in 46s
CI / test-python-backend (push) Successful in 39s
CI / test-python-document-crawler (push) Successful in 28s
CI / test-python-dsms-gateway (push) Successful in 22s
CI / validate-canonical-controls (push) Successful in 15s
Build + Deploy / trigger-orca (push) Successful in 2m15s
Build + Deploy / build-dsms-node (push) Successful in 11s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
Build + Deploy / build-admin-compliance (push) Successful in 12s
Build + Deploy / build-backend-compliance (push) Successful in 11s
Build + Deploy / build-ai-sdk (push) Successful in 40s
Build + Deploy / build-developer-portal (push) Successful in 10s
Build + Deploy / build-tts (push) Successful in 10s
Build + Deploy / build-document-crawler (push) Successful in 10s
Build + Deploy / build-dsms-gateway (push) Successful in 10s
CI / loc-budget (push) Failing after 13s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m33s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Successful in 46s
CI / test-python-backend (push) Successful in 39s
CI / test-python-document-crawler (push) Successful in 28s
CI / test-python-dsms-gateway (push) Successful in 22s
CI / validate-canonical-controls (push) Successful in 15s
Build + Deploy / trigger-orca (push) Successful in 2m15s
Add categoryHazardCap() with ISO 12100-proportional limits: - mechanical: 3x components (min 15, max 60) - electrical: 1x components (min 8, max 20) - secondary (thermal, noise, material): 4-8 - software/IT/organizational: 2-5 (minimal for machinery assessment) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -159,6 +159,7 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
|
|||||||
|
|
||||||
created := 0
|
created := 0
|
||||||
seenCatZone := make(map[string]bool)
|
seenCatZone := make(map[string]bool)
|
||||||
|
catCount := make(map[string]int)
|
||||||
for _, mp := range matchOutput.MatchedPatterns {
|
for _, mp := range matchOutput.MatchedPatterns {
|
||||||
// Narrative relevance filter: skip patterns whose zone/scenario
|
// Narrative relevance filter: skip patterns whose zone/scenario
|
||||||
// mentions machine-specific terms that don't appear in our components
|
// mentions machine-specific terms that don't appear in our components
|
||||||
@@ -167,6 +168,12 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, cat := range mp.HazardCats {
|
for _, cat := range mp.HazardCats {
|
||||||
|
// Per-category cap: limit hazards per category based on relevance
|
||||||
|
maxForCat := categoryHazardCap(cat, len(comps))
|
||||||
|
if catCount[cat] >= maxForCat {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Dedup by category + normalized zone
|
// Dedup by category + normalized zone
|
||||||
zoneKey := normalizeZoneKey(mp.ZoneDE)
|
zoneKey := normalizeZoneKey(mp.ZoneDE)
|
||||||
if zoneKey == "" {
|
if zoneKey == "" {
|
||||||
@@ -212,6 +219,7 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
if cerr == nil {
|
if cerr == nil {
|
||||||
created++
|
created++
|
||||||
|
catCount[cat]++
|
||||||
hazardIDsByCategory[cat] = hz.ID
|
hazardIDsByCategory[cat] = hz.ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -260,6 +260,67 @@ func isPatternRelevant(mp iace.PatternMatch, narrative string, compNames []strin
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// categoryHazardCap returns the maximum number of hazards to generate per category.
|
||||||
|
// Caps are based on typical ISO 12100 risk assessment proportions:
|
||||||
|
// - Core physical categories (mechanical, electrical): scale with component count
|
||||||
|
// - Secondary categories (thermal, noise, material): smaller fixed caps
|
||||||
|
// - Software/IT/organizational categories: minimal (these are usually covered by
|
||||||
|
// other standards like IEC 62443, not ISO 12100 machinery risk assessment)
|
||||||
|
func categoryHazardCap(cat string, componentCount int) int {
|
||||||
|
// Core machinery hazard categories — scale with complexity
|
||||||
|
switch cat {
|
||||||
|
case "mechanical_hazard":
|
||||||
|
// Typically 1-3 hazards per component (quetschen, scheren, stoss...)
|
||||||
|
cap := componentCount * 3
|
||||||
|
if cap < 15 {
|
||||||
|
cap = 15
|
||||||
|
}
|
||||||
|
if cap > 60 {
|
||||||
|
cap = 60
|
||||||
|
}
|
||||||
|
return cap
|
||||||
|
case "electrical_hazard":
|
||||||
|
// Typically 8-15 for a standard machine
|
||||||
|
cap := componentCount
|
||||||
|
if cap < 8 {
|
||||||
|
cap = 8
|
||||||
|
}
|
||||||
|
if cap > 20 {
|
||||||
|
cap = 20
|
||||||
|
}
|
||||||
|
return cap
|
||||||
|
case "pneumatic_hydraulic":
|
||||||
|
return 8
|
||||||
|
case "thermal_hazard":
|
||||||
|
return 6
|
||||||
|
case "noise_vibration":
|
||||||
|
return 4
|
||||||
|
case "material_environmental":
|
||||||
|
return 6
|
||||||
|
case "ergonomic", "ergonomic_hazard":
|
||||||
|
return 4
|
||||||
|
case "fire_explosion":
|
||||||
|
return 4
|
||||||
|
case "radiation_hazard", "emc_hazard":
|
||||||
|
return 3
|
||||||
|
// Software/IT/organizational — minimal for machinery assessment
|
||||||
|
case "safety_function_failure":
|
||||||
|
return 5
|
||||||
|
case "software_fault":
|
||||||
|
return 3
|
||||||
|
case "configuration_error":
|
||||||
|
return 3
|
||||||
|
case "hmi_error":
|
||||||
|
return 3
|
||||||
|
case "maintenance_hazard":
|
||||||
|
return 4
|
||||||
|
case "mode_confusion":
|
||||||
|
return 2
|
||||||
|
default:
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// normalizeZoneKey reduces a zone string to its core components for better dedup.
|
// normalizeZoneKey reduces a zone string to its core components for better dedup.
|
||||||
// E.g. "Schaltschrank, Sammelschiene" and "Schaltschrank-Innenraum, Sammelschienen"
|
// E.g. "Schaltschrank, Sammelschiene" and "Schaltschrank-Innenraum, Sammelschienen"
|
||||||
// should dedup to the same key.
|
// should dedup to the same key.
|
||||||
|
|||||||
Reference in New Issue
Block a user