77536f04b7
CI / detect-changes (push) Successful in 8s
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 4s
CI / validate-canonical-controls (push) Successful in 11s
CI / loc-budget (push) Successful in 14s
CI / go-lint (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 / 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
GET /projects/:id/hazards/:hid/risk-suggestion returns BreakPilot's justified starting values for BOTH risk models per hazard: - EN-62061-style F/W/P/S (the Excel format the professional knows) - Fine-Kinney P/E/C (US-recognized) each with a plain-language justification + the visible formula. Read-only and computed from public-data anchors (ESAW/NIOSH/OSHA via the engine estimators) — the professional adjusts the values; no norm table is stored or reproduced. Adds EstimateFrequency (lifecycle -> 1-5) and BuildRiskSuggestion. Go SDK has no OpenAPI baseline, so the only contract surface is the frontend consumer (the new Risikobewertung tab, next). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
112 lines
4.0 KiB
Go
112 lines
4.0 KiB
Go
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"`
|
||
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"
|
||
}
|
||
|
||
// 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), fmt.Sprintf("Wahrscheinlichkeit W aus ESAW-Haeufigkeit der Kontaktart '%s'", modeLabel)},
|
||
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",
|
||
},
|
||
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
|
||
}
|