feat(iace): Risikograph EN ISO 13849-1 PLr + Methoden-Kopf im Bericht

Phase 17 of the risk-assessment polish. Two pieces:

A) PLr per EN ISO 13849-1 Anhang A (Risikograph)
   - HazardPattern.DefaultAvoidability (1 = P1, 2 = P2). Optional;
     defaults to P1 if unset (conservative — operator can raise after
     review).
   - ComputePLr(s,f,p) implements the canonical 8-leaf binary tree
     (S1F1P1 -> a, ..., S2F2P2 -> e). Pinned by 8 table-driven tests.
   - SeverityToS / ExposureToF map the existing 1-5 fields to the
     binary S/F at the documented threshold (3).
   - At project initialise, every hazard's Description is appended
     with "Risikograph EN ISO 13849-1 (Anhang A): S2 · F1 · P1 -> PLr c"
     so the audit value is visible without leaving the hazard view.
   - PatternMatch carries DefaultAvoidability so the init handler can
     pick it up without a second pattern lookup.

B) Methoden-Kopf am Bericht
   - GET /clarifications.html now opens with a standardised methodology
     block: ISO 12100 Anhang B (hazard ID) + ISO 13849-1 Anhang A
     (PLr graph) + ISO 12100 6.2/6.3/6.4 (reduction hierarchy). Same
     wording on every export, ready for the Anlagenbauer-Uebergabe.
   - Only norm identifiers — no norm text reproduced.

C) ISO12100Section in Hazard Description
   - When a pattern is labeled with ISO12100Section, the hazard
     description gets a "Klassifikation: EN ISO 12100 Anhang B,
     Abschnitt 6.3.5.4" suffix. Provenance for the auditor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-17 02:03:10 +02:00
parent 71d31c914b
commit 2afa5a179b
5 changed files with 126 additions and 0 deletions
@@ -219,6 +219,27 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
// scenario itself. Only the aggregated norm-references
// block is appended below for an at-a-glance audit trail.
desc := mp.ScenarioDE
// Phase 17: PLr per EN ISO 13849-1 Anhang A. The graph
// inputs come from the pattern's DefaultSeverity/Exposure
// (mapped to S1/S2 and F1/F2 at threshold 3) plus
// DefaultAvoidability (P1/P2). If avoidability is unset
// we default to P1 — the conservative direction is
// downward (lower PLr), the operator can raise it
// manually after expert review.
avoid := 1
if mp.DefaultAvoidability == 2 {
avoid = 2
}
if mp.DefaultSeverity > 0 && mp.DefaultExposure > 0 {
sBin := iace.SeverityToS(mp.DefaultSeverity)
fBin := iace.ExposureToF(mp.DefaultExposure)
plr := iace.ComputePLr(sBin, fBin, avoid)
desc += fmt.Sprintf("\n\nRisikograph EN ISO 13849-1 (Anhang A): S%d · F%d · P%d → PLr %s",
sBin, fBin, avoid, plr)
}
if mp.ISO12100Section != "" {
desc += "\n\nKlassifikation: EN ISO 12100 Anhang B, Abschnitt " + mp.ISO12100Section
}
hz, cerr := h.store.CreateHazard(ctx, iace.CreateHazardRequest{
ProjectID: projectID,