package iace import ( "fmt" "strings" ) // Fine-Kinney risk model — BreakPilot's deterministic, citable risk backbone. // // Method: W.T. Fine, "Mathematical Evaluations for Controlling Hazards" // (1971, US Naval Ordnance Laboratory report) and G.F. Kinney & A.D. Wiruth, // "Practical Risk Analysis for Safety Management" (1976). Risk = P x E x C. // It is a PUBLISHED, freely-usable method — not a copyrighted DIN/Beuth/ISO // standard — and is widely used in industry (incl. CE-marking risk analysis). // // We derive SUGGESTED P/E/C values deterministically from PUBLIC, permissively // licensed sources (Eurostat ESAW frequencies, NIOSH/OSHA/MIL-STD-882 injury // outcomes — see DATA_SOURCES.md), each with a plain-language justification. // The professional then ADJUSTS them (e.g. from his own licensed DIN/Beuth // data) — the tool only supplies the formula and computes. We never reproduce // norm tables. // FKParam is a suggested Fine-Kinney parameter value plus why we chose it. type FKParam struct { Value float64 `json:"value"` Justification string `json:"justification"` } // FKAssessment is a full suggested Fine-Kinney assessment for one hazard. type FKAssessment struct { Probability FKParam `json:"probability"` // P: 0.1 .. 10 Exposure FKParam `json:"exposure"` // E: 0.5 .. 10 Consequence FKParam `json:"consequence"` // C: 1 .. 100 Score float64 `json:"score"` // R = P * E * C Band string `json:"band"` // Fine-Kinney risk band label Action string `json:"action"` // suggested urgency of action } // fkProbabilityByMode maps a contact mode to a Fine-Kinney probability value, // anchored to the ESAW relative frequency of that injury mechanism. var fkProbabilityByMode = map[string]float64{ "impact_stationary": 6, "crushing": 6, "struck_by": 6, "ergonomic": 6, "cutting": 3, "entanglement": 3, "shearing": 3, "electrical": 3, "thermal": 3, "fall": 3, "chemical": 1, "pressure_burst": 1, "radiation": 0.5, } // fkConsequenceFromSeverity maps our pattern-specific, de-biased severity (1-5) // onto the published Fine-Kinney consequence scale. Using the per-hazard // severity (not a coarse mode constant) preserves the ranking signal. // 1=first aid, 3=disability, 7=serious injury, 15=a fatality, 40=multiple. func fkConsequenceFromSeverity(s int) float64 { switch { case s >= 5: return 40 case s == 4: return 15 case s == 3: return 7 case s == 2: return 3 default: return 1 } } // SuggestFineKinney builds a justified Fine-Kinney assessment from public-data // anchors. Inputs are the hazard's categories, scenario text and the project's // lifecycle phases. Values are SUGGESTIONS the professional adjusts. func SuggestFineKinney(cats []string, scenario string, lifecyclePhases []string, defaultSeverity int) FKAssessment { mode := DetectContactMode(cats, scenario) p := 3.0 if v, ok := fkProbabilityByMode[mode]; ok { p = v } s := EstimateSeverity(cats, scenario, defaultSeverity) c := fkConsequenceFromSeverity(s) e := fkExposure(lifecyclePhases) modeLabel := mode if modeLabel == "" { modeLabel = "unbestimmt" } a := FKAssessment{ Probability: FKParam{p, "Eintrittswahrscheinlichkeit aus ESAW-Haeufigkeit der Kontaktart '" + modeLabel + "'"}, Exposure: FKParam{e.value, e.reason}, Consequence: FKParam{c, fmt.Sprintf("Konsequenz aus Schwere-Einstufung S%d (NIOSH/OSHA/MIL-STD-882-Verletzungsbild)", s)}, } a.Score, a.Band, a.Action = ComputeFineKinney(p, e.value, c) return a } type fkExp struct { value float64 reason string } // fkExposure maps the active lifecycle phases to a Fine-Kinney exposure value // (how often a person is exposed to the task). func fkExposure(phases []string) fkExp { has := func(needle string) bool { for _, p := range phases { if strings.Contains(p, needle) { return true } } return false } switch { case has("normal_operation") || has("auto_operation") || has("manual_operation"): return fkExp{6, "Exposition: Normalbetrieb (taeglich/dauernd)"} case has("setup") || has("maintenance") || has("cleaning") || has("changeover"): return fkExp{3, "Exposition: Einricht-/Wartungs-/Reinigungstaetigkeit (gelegentlich)"} case len(phases) > 0: return fkExp{1, "Exposition: seltene Lebensphase (wenige Male pro Jahr)"} default: return fkExp{3, "Exposition: angenommen gelegentlich (keine Lebensphase angegeben)"} } } // ComputeFineKinney returns the Fine-Kinney risk score (P*E*C) and the // published risk band + suggested action urgency. The professional may pass // his own adjusted P/E/C here (e.g. derived from his licensed DIN/Beuth data) — // the tool only computes; it stores no norm table. func ComputeFineKinney(p, e, c float64) (score float64, band, action string) { score = p * e * c switch { case score > 400: return score, "sehr hoch", "Taetigkeit einstellen / sofortige Massnahmen" case score > 200: return score, "hoch", "sofortige Sanierung erforderlich" case score > 70: return score, "wesentlich", "Sanierung erforderlich" case score > 20: return score, "moeglich", "Aufmerksamkeit, Massnahmen planen" default: return score, "gering", "ggf. akzeptabel, beobachten" } }