feat(iace): add Fine-Kinney risk model (citable, free, US-recognized)
CI / detect-changes (push) Successful in 6s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Failing after 5s
CI / validate-canonical-controls (push) Successful in 11s
CI / loc-budget (push) Successful in 15s
CI / go-lint (push) Has been skipped
CI / test-go (push) Failing after 38s
CI / iace-gt-coverage (push) Successful in 23s
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Has been skipped
CI / detect-changes (push) Successful in 6s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Failing after 5s
CI / validate-canonical-controls (push) Successful in 11s
CI / loc-budget (push) Successful in 15s
CI / go-lint (push) Has been skipped
CI / test-go (push) Failing after 38s
CI / iace-gt-coverage (push) Successful in 23s
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Has been skipped
Fine-Kinney (Fine 1971 / Kinney-Wiruth 1976): Risk = Probability x Exposure x Consequence — a PUBLISHED, freely-usable method (not a DIN/Beuth/ISO standard), widely used incl. CE-marking. Gives the professional a second, US-recognized model alongside the EN-62061-style one; German exporters get both for free and adjust with their own licensed norm data. risk_fine_kinney.go: SuggestFineKinney derives justified P/E/C from public anchors (ESAW frequency -> P, lifecycle -> E, de-biased severity -> C on the Fine-Kinney consequence scale) + ComputeFineKinney(p,e,c) so the professional can override with his own values. No norm table stored. GT benchmark (rank concordance vs the professional): Fine-Kinney 75.4% — beats the EN-62061-style model (69.3%) and the raw engine (57%). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user