Files
breakpilot-compliance/ai-compliance-sdk/internal/iace/risk_suggestion.go
T
Benjamin Admin 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
feat(iace): dual-model risk-suggestion endpoint for Risikobewertung tab
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>
2026-06-09 15:35:39 +02:00

112 lines
4.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}