package iace import ( "fmt" "strings" ) // Dual-model risk suggestion for the "Risikobewertung" tab. BreakPilot proposes // justified starting values for BOTH a EN-62061-style model (F/W/P/S) and the // Fine-Kinney model (P/E/C); the professional adjusts them (e.g. from his own // licensed DIN/Beuth data). We expose the FORMULAS and computed values only — // no norm table is stored or reproduced. // SuggestedValue is a proposed parameter value plus the plain-language reason. type SuggestedValue struct { Value float64 `json:"value"` Justification string `json:"justification"` } // EN62061Suggestion is the EN-62061-style risk (the Excel format the // professional knows): R = S * (F + W + P). type EN62061Suggestion struct { Severity SuggestedValue `json:"severity"` Frequency SuggestedValue `json:"frequency"` Probability SuggestedValue `json:"probability"` Avoidance SuggestedValue `json:"avoidance"` Score int `json:"score"` Level string `json:"level"` Formula string `json:"formula"` } // FineKinneySuggestion is the Fine-Kinney risk (US-recognized): R = P * E * C. type FineKinneySuggestion struct { Probability SuggestedValue `json:"probability"` Exposure SuggestedValue `json:"exposure"` Consequence SuggestedValue `json:"consequence"` Score float64 `json:"score"` Band string `json:"band"` Action string `json:"action"` Formula string `json:"formula"` } // RiskSuggestion carries both models for one hazard. type RiskSuggestion struct { HazardID string `json:"hazard_id"` ContactMode string `json:"contact_mode"` EN62061 EN62061Suggestion `json:"en62061"` FineKinney FineKinneySuggestion `json:"fine_kinney"` DataSource *RiskEvidence `json:"data_source,omitempty"` Note string `json:"note"` } // BuildRiskSuggestion derives both models' justified starting values from the // hazard's category/scenario/lifecycle, using only public-data anchors. func BuildRiskSuggestion(hz *Hazard) RiskSuggestion { cats := []string{hz.Category} scenario := hz.Scenario if scenario == "" { scenario = hz.Name } lifecycle := splitLifecyclePhases(hz.LifecyclePhase) mode := DetectContactMode(cats, scenario) modeLabel := mode if modeLabel == "" { modeLabel = "unbestimmt" } // Cite the actual public statistic + license for the W anchor where documented. wJustification := fmt.Sprintf("Wahrscheinlichkeit W aus ESAW-Haeufigkeit der Kontaktart '%s'", modeLabel) var dataSource *RiskEvidence if ev, ok := RiskEvidenceFor(mode); ok { wJustification += fmt.Sprintf(" (%s: %s; %s)", ev.Label, ev.Stat, ev.Attribution) dataSource = &ev } // EN-62061-style (F/W/P/S) s := EstimateSeverity(cats, scenario, 0) f := EstimateFrequency(lifecycle) w := EstimateProbabilityW(cats, scenario) p := EstimateAvoidabilityP(cats, scenario) idx, level := EstimateRiskLevel(s, f, w, p) // Fine-Kinney (P/E/C) fk := SuggestFineKinney(cats, scenario, lifecycle, 0) return RiskSuggestion{ HazardID: hz.ID.String(), ContactMode: modeLabel, EN62061: EN62061Suggestion{ Severity: SuggestedValue{float64(s), fmt.Sprintf("Schwere S%d aus Verletzungsbild der Kontaktart '%s' (NIOSH/OSHA/MIL-STD-882)", s, modeLabel)}, Frequency: SuggestedValue{float64(f), "Haeufigkeit F aus Lebensphasen-Exposition des Projekts"}, Probability: SuggestedValue{float64(w), wJustification}, Avoidance: SuggestedValue{float64(p), fmt.Sprintf("Vermeidbarkeit P aus Kinematik der Kontaktart '%s'", modeLabel)}, Score: idx, Level: level, Formula: "R = S × (F + W + P)", }, FineKinney: FineKinneySuggestion{ Probability: SuggestedValue{fk.Probability.Value, fk.Probability.Justification}, Exposure: SuggestedValue{fk.Exposure.Value, fk.Exposure.Justification}, Consequence: SuggestedValue{fk.Consequence.Value, fk.Consequence.Justification}, Score: fk.Score, Band: fk.Band, Action: fk.Action, Formula: "R = P × E × C", }, DataSource: dataSource, Note: "Begruendete Vorschlagswerte (BreakPilot, oeffentliche Datenquellen). Vom Sachverstaendigen anpassbar.", } } func splitLifecyclePhases(s string) []string { var out []string for _, p := range strings.Split(s, ",") { if p = strings.TrimSpace(p); p != "" { out = append(out, p) } } return out }