feat(iace): integrate ISO 12100 machine risk model with 4-factor assessment
All checks were successful
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Successful in 36s
CI/CD / test-python-backend-compliance (push) Successful in 36s
CI/CD / test-python-document-crawler (push) Successful in 22s
CI/CD / test-python-dsms-gateway (push) Successful in 18s
CI/CD / validate-canonical-controls (push) Successful in 12s
CI/CD / Deploy (push) Successful in 2s
All checks were successful
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Successful in 36s
CI/CD / test-python-backend-compliance (push) Successful in 36s
CI/CD / test-python-document-crawler (push) Successful in 22s
CI/CD / test-python-dsms-gateway (push) Successful in 18s
CI/CD / validate-canonical-controls (push) Successful in 12s
CI/CD / Deploy (push) Successful in 2s
Add dual-mode risk engine: legacy S×E×P (avoidance=0) and ISO mode S×F×P×A (avoidance>=1) with new thresholds (low/medium/high/very_high/not_acceptable). - 150+ hazard library entries across 28 categories incl. physical hazards (mechanical, electrical, thermal, pneumatic/hydraulic, noise/vibration, ergonomic, material/environmental) - 160-entry protective measures library with 3-step hierarchy validation (design → protective → information) - 25 lifecycle phases, 20 affected person roles, 50 evidence types - 10 verification methods (expanded from 7) - New API endpoints: lifecycle-phases, roles, evidence-types, protective-measures-library, validate-mitigation-hierarchy - DB migrations 018+019 for extended schema - Frontend: 4-slider risk assessment, hierarchy warnings, measures library modal - MkDocs wiki updated with ISO mode docs and legal notice (no norm text) All content uses original wording — norms referenced as methodology only. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -71,11 +71,13 @@ func clampFloat(v, lo, hi float64) float64 {
|
||||
|
||||
// CalculateInherentRisk computes the inherent risk score.
|
||||
//
|
||||
// Formula:
|
||||
// - avoidance == 0: S × E × P (backward-compatible, no avoidance factor)
|
||||
// - avoidance > 0: S × E × P × (A / 3.0) (3 = neutral, no influence)
|
||||
// Dual-mode formula for backward compatibility:
|
||||
// - avoidance == 0: Legacy S × E × P (no avoidance factor)
|
||||
// - avoidance >= 1: ISO 12100 mode S × F × P × A (direct multiplication)
|
||||
//
|
||||
// In ISO mode, the factors represent:
|
||||
// - S: Severity (1-5), F: Frequency/Exposure (1-5), P: Probability (1-5), A: Avoidance (1-5)
|
||||
//
|
||||
// Avoidance scale: 1=leicht vermeidbar, 3=neutral, 5=nicht vermeidbar.
|
||||
// Each factor is expected in the range 1-5 and will be clamped if out of range.
|
||||
func (e *RiskEngine) CalculateInherentRisk(severity, exposure, probability, avoidance int) float64 {
|
||||
s := clamp(severity, 1, 5)
|
||||
@@ -85,8 +87,9 @@ func (e *RiskEngine) CalculateInherentRisk(severity, exposure, probability, avoi
|
||||
if avoidance <= 0 {
|
||||
return base
|
||||
}
|
||||
// ISO 12100 mode: direct S × F × P × A multiplication (no /3.0 normalization)
|
||||
a := clamp(avoidance, 1, 5)
|
||||
return base * (float64(a) / 3.0)
|
||||
return base * float64(a)
|
||||
}
|
||||
|
||||
// CalculateControlEffectiveness computes the control effectiveness score.
|
||||
@@ -143,6 +146,68 @@ func (e *RiskEngine) DetermineRiskLevel(residualRisk float64) RiskLevel {
|
||||
}
|
||||
}
|
||||
|
||||
// DetermineRiskLevelISO classifies the inherent risk using ISO 12100 thresholds.
|
||||
//
|
||||
// Thresholds (for S×F×P×A, max 625):
|
||||
// - > 300: not_acceptable
|
||||
// - 151-300: very_high
|
||||
// - 61-150: high
|
||||
// - 21-60: medium
|
||||
// - 1-20: low
|
||||
func (e *RiskEngine) DetermineRiskLevelISO(risk float64) RiskLevel {
|
||||
switch {
|
||||
case risk > 300:
|
||||
return RiskLevelNotAcceptable
|
||||
case risk >= 151:
|
||||
return RiskLevelVeryHigh
|
||||
case risk >= 61:
|
||||
return RiskLevelHigh
|
||||
case risk >= 21:
|
||||
return RiskLevelMedium
|
||||
default:
|
||||
return RiskLevelLow
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateProtectiveMeasureHierarchy checks if an information-only measure
|
||||
// (ReductionType "information") is being used as the primary mitigation
|
||||
// when design or technical measures could be applied.
|
||||
// Returns a list of warning messages.
|
||||
func (e *RiskEngine) ValidateProtectiveMeasureHierarchy(reductionType ReductionType, existingMitigations []Mitigation) []string {
|
||||
var warnings []string
|
||||
|
||||
if reductionType != ReductionTypeInformation {
|
||||
return warnings
|
||||
}
|
||||
|
||||
// Check if there are any design or protective measures already
|
||||
hasDesign := false
|
||||
hasProtective := false
|
||||
for _, m := range existingMitigations {
|
||||
if m.Status == MitigationStatusRejected {
|
||||
continue
|
||||
}
|
||||
switch m.ReductionType {
|
||||
case ReductionTypeDesign:
|
||||
hasDesign = true
|
||||
case ReductionTypeProtective:
|
||||
hasProtective = true
|
||||
}
|
||||
}
|
||||
|
||||
if !hasDesign && !hasProtective {
|
||||
warnings = append(warnings,
|
||||
"Hinweismassnahmen (Typ 3) duerfen NICHT als alleinige Primaermassnahme verwendet werden. "+
|
||||
"Pruefen Sie zuerst, ob konstruktive (Typ 1) oder technische Schutzmassnahmen (Typ 2) moeglich sind.")
|
||||
} else if !hasDesign {
|
||||
warnings = append(warnings,
|
||||
"Es liegen keine konstruktiven Massnahmen (Typ 1) vor. "+
|
||||
"Pruefen Sie, ob eine inhaerent sichere Konstruktion die Gefaehrdung beseitigen kann.")
|
||||
}
|
||||
|
||||
return warnings
|
||||
}
|
||||
|
||||
// IsAcceptable determines whether the residual risk is acceptable based on
|
||||
// the ALARP (As Low As Reasonably Practicable) principle and EU AI Act thresholds.
|
||||
//
|
||||
@@ -200,7 +265,14 @@ func (e *RiskEngine) ComputeRisk(req RiskComputeInput) (*RiskComputeResult, erro
|
||||
inherentRisk := e.CalculateInherentRisk(req.Severity, req.Exposure, req.Probability, req.Avoidance)
|
||||
controlEff := e.CalculateControlEffectiveness(req.ControlMaturity, req.ControlCoverage, req.TestEvidence)
|
||||
residualRisk := e.CalculateResidualRisk(req.Severity, req.Exposure, req.Probability, controlEff)
|
||||
riskLevel := e.DetermineRiskLevel(residualRisk)
|
||||
|
||||
// ISO 12100 mode uses ISO thresholds for inherent risk classification
|
||||
var riskLevel RiskLevel
|
||||
if req.Avoidance >= 1 {
|
||||
riskLevel = e.DetermineRiskLevelISO(inherentRisk)
|
||||
} else {
|
||||
riskLevel = e.DetermineRiskLevel(residualRisk)
|
||||
}
|
||||
acceptable, reason := e.IsAcceptable(residualRisk, false, req.HasJustification)
|
||||
|
||||
return &RiskComputeResult{
|
||||
|
||||
Reference in New Issue
Block a user