feat(iace): cite ESAW source + license on risk-frequency anchors
Surfaces the public-statistics provenance for the contact-mode probability tiers so generated risk numbers are auditable and attributed (not RAG — ~a dozen stable aggregate facts are better as a license-tagged code table). - risk_data_sources.go: RiskEvidence register (Eurostat ESAW figures + CC BY 4.0 attribution) for the documented contact modes; RiskDataSourcesNote. - risk_suggestion.go: the W justification now cites the actual ESAW share + license where documented; RiskSuggestion gains a data_source field. - GET /iace/risk-data-sources returns the evidence register + attribution. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -47,3 +47,14 @@ func (h *IACEHandler) GetRiskMatrix(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, iace.BuildRiskMatrix(hazards))
|
c.JSON(http.StatusOK, iace.BuildRiskMatrix(hazards))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRiskDataSources handles GET /risk-data-sources.
|
||||||
|
// Returns the license-tagged public-statistics evidence register (Eurostat ESAW,
|
||||||
|
// CC BY 4.0) that anchors the risk-frequency tiers, plus the overall attribution
|
||||||
|
// note — so an auditor can see WHERE the risk numbers come from.
|
||||||
|
func (h *IACEHandler) GetRiskDataSources(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"note": iace.RiskDataSourcesNote,
|
||||||
|
"evidence": iace.AllRiskEvidence(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ func registerIACERoutes(v1 *gin.RouterGroup, h *handlers.IACEHandler) {
|
|||||||
iaceRoutes.GET("/projects/:id/hazards/:hid/risk-suggestion", h.GetRiskSuggestion)
|
iaceRoutes.GET("/projects/:id/hazards/:hid/risk-suggestion", h.GetRiskSuggestion)
|
||||||
iaceRoutes.GET("/projects/:id/risk-summary", h.GetRiskSummary)
|
iaceRoutes.GET("/projects/:id/risk-summary", h.GetRiskSummary)
|
||||||
iaceRoutes.GET("/projects/:id/risk-matrix", h.GetRiskMatrix)
|
iaceRoutes.GET("/projects/:id/risk-matrix", h.GetRiskMatrix)
|
||||||
|
iaceRoutes.GET("/risk-data-sources", h.GetRiskDataSources)
|
||||||
iaceRoutes.GET("/projects/:id/suggested-norms", h.SuggestProjectNorms)
|
iaceRoutes.GET("/projects/:id/suggested-norms", h.SuggestProjectNorms)
|
||||||
iaceRoutes.POST("/projects/:id/hazards/:hid/reassess", h.ReassessRisk)
|
iaceRoutes.POST("/projects/:id/hazards/:hid/reassess", h.ReassessRisk)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package iace
|
||||||
|
|
||||||
|
import "sort"
|
||||||
|
|
||||||
|
// Evidence / citation layer for the risk-frequency anchors. Each entry is an
|
||||||
|
// aggregate, permissively-licensed public statistic (Eurostat ESAW, CC BY 4.0)
|
||||||
|
// that anchors the RELATIVE ordering of a contact mode's probability tier. The
|
||||||
|
// tier VALUES in contactModeTable are BreakPilot's own GT-calibrated numbers —
|
||||||
|
// this table only carries the provenance so generated risk numbers are
|
||||||
|
// auditable and correctly attributed. No raw dataset is vendored; only these
|
||||||
|
// aggregate facts. Excluded by license: DGUV, DIN/Beuth/ISO/IEC. See
|
||||||
|
// DATA_SOURCES.md. RAG/Qdrant ingestion is deliberately NOT used here: ~a dozen
|
||||||
|
// stable aggregate facts are better served by a license-tagged code table than
|
||||||
|
// by vector retrieval.
|
||||||
|
|
||||||
|
// RiskEvidence is the public-statistics provenance for one contact mode.
|
||||||
|
type RiskEvidence struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Label string `json:"label"` // German contact-mode label
|
||||||
|
Stat string `json:"stat"` // the cited aggregate figure
|
||||||
|
Source string `json:"source"` // "Eurostat (ESAW)"
|
||||||
|
License string `json:"license"` // "CC BY 4.0"
|
||||||
|
Attribution string `json:"attribution"` // ready-to-print citation line
|
||||||
|
Retrieved string `json:"retrieved"` // retrieval month
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
esawSource = "Eurostat (ESAW)"
|
||||||
|
esawLicense = "CC BY 4.0"
|
||||||
|
esawAttribution = "Quelle: Eurostat (ESAW), CC BY 4.0"
|
||||||
|
esawRetrieved = "2026-06"
|
||||||
|
)
|
||||||
|
|
||||||
|
func esawEvidence(mode, label, stat string) RiskEvidence {
|
||||||
|
return RiskEvidence{Mode: mode, Label: label, Stat: stat, Source: esawSource,
|
||||||
|
License: esawLicense, Attribution: esawAttribution, Retrieved: esawRetrieved}
|
||||||
|
}
|
||||||
|
|
||||||
|
// contactModeEvidence holds only the contact modes for which a specific public
|
||||||
|
// figure is documented; other modes are anchored by the ESAW ordering and
|
||||||
|
// GT-calibrated without a single citable share, so they carry no fabricated stat.
|
||||||
|
var contactModeEvidence = map[string]RiskEvidence{
|
||||||
|
"impact_stationary": esawEvidence("impact_stationary", "Anstoßen an ruhendem Objekt", "~24 % der Arbeitsunfälle"),
|
||||||
|
"struck_by": esawEvidence("struck_by", "Getroffen von bewegtem Objekt", "~13 % (nicht-tödlich) / ~24 % (tödlich)"),
|
||||||
|
"crushing": esawEvidence("crushing", "Quetschen / Einklemmen", "~14 % der tödlichen Arbeitsunfälle"),
|
||||||
|
"cutting": esawEvidence("cutting", "Kontakt mit scharfem Gegenstand", "~15 % der Arbeitsunfälle"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// RiskEvidenceFor returns the documented public statistic for a contact mode.
|
||||||
|
func RiskEvidenceFor(mode string) (RiskEvidence, bool) {
|
||||||
|
e, ok := contactModeEvidence[mode]
|
||||||
|
return e, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllRiskEvidence returns the full evidence register (sorted), for a
|
||||||
|
// "Datenquellen" panel / risk-assessment export attribution.
|
||||||
|
func AllRiskEvidence() []RiskEvidence {
|
||||||
|
out := make([]RiskEvidence, 0, len(contactModeEvidence))
|
||||||
|
for _, e := range contactModeEvidence {
|
||||||
|
out = append(out, e)
|
||||||
|
}
|
||||||
|
sort.Slice(out, func(i, j int) bool { return out[i].Mode < out[j].Mode })
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// RiskDataSourcesNote is the overall attribution shown wherever engine risk
|
||||||
|
// numbers appear, satisfying the ESAW CC BY 4.0 source-acknowledgement.
|
||||||
|
const RiskDataSourcesNote = "Häufigkeits-/Wahrscheinlichkeits-Tiers verankert am öffentlichen Kontaktmodus-Ranking von " +
|
||||||
|
esawSource + " (" + esawLicense + "), kalibriert an BreakPilot-Ground-Truth. Keine Norm-Tabelle reproduziert; DGUV/DIN/ISO ausgeschlossen."
|
||||||
@@ -46,6 +46,7 @@ type RiskSuggestion struct {
|
|||||||
ContactMode string `json:"contact_mode"`
|
ContactMode string `json:"contact_mode"`
|
||||||
EN62061 EN62061Suggestion `json:"en62061"`
|
EN62061 EN62061Suggestion `json:"en62061"`
|
||||||
FineKinney FineKinneySuggestion `json:"fine_kinney"`
|
FineKinney FineKinneySuggestion `json:"fine_kinney"`
|
||||||
|
DataSource *RiskEvidence `json:"data_source,omitempty"`
|
||||||
Note string `json:"note"`
|
Note string `json:"note"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,6 +66,14 @@ func BuildRiskSuggestion(hz *Hazard) RiskSuggestion {
|
|||||||
modeLabel = "unbestimmt"
|
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)
|
// EN-62061-style (F/W/P/S)
|
||||||
s := EstimateSeverity(cats, scenario, 0)
|
s := EstimateSeverity(cats, scenario, 0)
|
||||||
f := EstimateFrequency(lifecycle)
|
f := EstimateFrequency(lifecycle)
|
||||||
@@ -81,7 +90,7 @@ func BuildRiskSuggestion(hz *Hazard) RiskSuggestion {
|
|||||||
EN62061: EN62061Suggestion{
|
EN62061: EN62061Suggestion{
|
||||||
Severity: SuggestedValue{float64(s), fmt.Sprintf("Schwere S%d aus Verletzungsbild der Kontaktart '%s' (NIOSH/OSHA/MIL-STD-882)", s, modeLabel)},
|
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"},
|
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)},
|
Probability: SuggestedValue{float64(w), wJustification},
|
||||||
Avoidance: SuggestedValue{float64(p), fmt.Sprintf("Vermeidbarkeit P aus Kinematik der Kontaktart '%s'", modeLabel)},
|
Avoidance: SuggestedValue{float64(p), fmt.Sprintf("Vermeidbarkeit P aus Kinematik der Kontaktart '%s'", modeLabel)},
|
||||||
Score: idx,
|
Score: idx,
|
||||||
Level: level,
|
Level: level,
|
||||||
@@ -96,6 +105,7 @@ func BuildRiskSuggestion(hz *Hazard) RiskSuggestion {
|
|||||||
Action: fk.Action,
|
Action: fk.Action,
|
||||||
Formula: "R = P × E × C",
|
Formula: "R = P × E × C",
|
||||||
},
|
},
|
||||||
|
DataSource: dataSource,
|
||||||
Note: "Begruendete Vorschlagswerte (BreakPilot, oeffentliche Datenquellen). Vom Sachverstaendigen anpassbar.",
|
Note: "Begruendete Vorschlagswerte (BreakPilot, oeffentliche Datenquellen). Vom Sachverstaendigen anpassbar.",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user