package iace // SILPLResult contains the deterministic SIL/PL recommendation // derived from the risk assessment S×E×P values, plus expert override fields. type SILPLResult struct { RecommendedPL string `json:"recommended_pl"` // "a", "b", "c", "d", "e" RecommendedSIL string `json:"recommended_sil"` // "none", "SIL 1", "SIL 2", "SIL 3" RiskGraphInput RiskGraphIn `json:"risk_graph_input"` // Expert override fields (filled by Fachmann) OverriddenPL string `json:"overridden_pl,omitempty"` OverriddenSIL string `json:"overridden_sil,omitempty"` OverrideReason string `json:"override_reason,omitempty"` NormReferences []string `json:"norm_references,omitempty"` ExpertValidated bool `json:"expert_validated"` ValidatedBy string `json:"validated_by,omitempty"` ValidatedAt string `json:"validated_at,omitempty"` } // RiskGraphIn shows the mapped ISO risk graph factors. type RiskGraphIn struct { S string `json:"s"` // "S1" or "S2" F string `json:"f"` // "F1" or "F2" P string `json:"p"` // "P1" or "P2" } // CalculateSILPL derives Performance Level (PL) and Safety Integrity Level (SIL) // from the IACE risk assessment factors (Severity 1-5, Exposure 1-5, Probability 1-5). // // This is a deterministic mapping based on the general risk graph principle: // - Severity ≥ 4 → S2 (serious/fatal), else S1 (minor/reversible) // - Exposure ≥ 3 → F2 (frequent/continuous), else F1 (rare/short) // - Probability ≥ 4 → P2 (hardly avoidable), else P1 (avoidable) // // NO normative text is reproduced. This is our own implementation. func CalculateSILPL(severity, exposure, probability int) SILPLResult { s, f, p := mapToRiskGraph(severity, exposure, probability) pl := calculatePL(s, f, p) sil := plToSIL(pl) return SILPLResult{ RecommendedPL: pl, RecommendedSIL: sil, RiskGraphInput: RiskGraphIn{S: s, F: f, P: p}, } } // mapToRiskGraph converts 1-5 scale factors to binary S1/S2, F1/F2, P1/P2. func mapToRiskGraph(severity, exposure, probability int) (string, string, string) { s := "S1" if severity >= 4 { s = "S2" } f := "F1" if exposure >= 3 { f = "F2" } p := "P1" if probability >= 4 { p = "P2" } return s, f, p } // calculatePL determines Performance Level from the risk graph. // // Risk graph (own formulation, no norm text): // // S1+F1+P1 ��� a (lowest) // S1+F1+P2 → b // S1+F2+P1 → b // S1+F2+P2 → c // S2+F1+P1 → c // S2+F1+P2 → d // S2+F2+P1 → d // S2+F2+P2 → e (highest) func calculatePL(s, f, p string) string { if s == "S1" { if f == "F1" { if p == "P1" { return "a" } return "b" } // F2 if p == "P1" { return "b" } return "c" } // S2 if f == "F1" { if p == "P1" { return "c" } return "d" } // S2+F2 if p == "P1" { return "d" } return "e" } // plToSIL maps Performance Level to Safety Integrity Level. // // PL a, b → no SIL requirement // PL c → SIL 1 // PL d → SIL 2 // PL e → SIL 3 func plToSIL(pl string) string { switch pl { case "a", "b": return "none" case "c": return "SIL 1" case "d": return "SIL 2" case "e": return "SIL 3" default: return "none" } } // GetEffectivePL returns the expert-overridden PL if set, otherwise the recommendation. func (r *SILPLResult) GetEffectivePL() string { if r.OverriddenPL != "" { return r.OverriddenPL } return r.RecommendedPL } // GetEffectiveSIL returns the expert-overridden SIL if set, otherwise the recommendation. func (r *SILPLResult) GetEffectiveSIL() string { if r.OverriddenSIL != "" { return r.OverriddenSIL } return r.RecommendedSIL }