feat(iace): norm references in mitigations + aggregated norm panel per hazard

Library measures carry NormReferences (EN/IEC/ISO/DIN/TRBS/TRGS Ziff./Kap./
Pos.) but they were dropped on persist: CreateMitigationRequest only
wrote Name + Description. The Fachmann benchmark file lists Normen for
34 of 60 hazards — the engine had this data already but lost it on the
way to the UI.

Fix without DB schema change:
- Mitigation.Description gets a "Normen: EN 60204-1 Ziff. 6.2 | EN 61140"
  line appended when the measure has NormReferences. Pipe separator keeps
  the inline panel short and grep-friendly.
- After all mitigations land, the aggregated dedup'd norm list for the
  hazard is appended to Hazard.Description as a single "Referenzierte
  Normen: ..." line so the UI can show one panel per hazard without
  scanning every mitigation.

Audit of library coverage (per-pattern) showed GT-Bremse Normen are
generally present and richer:
- HP1640 covers GT 2.2 (EN 60204-1 Ziff. 6.2, Ziff. 8.2.3, EN 61140 +)
- HP1641 covers GT 2.4 (EN 60204-1 Ziff. 8.2.6 +)
- HP1605 covers GT 1.7 (ISO 10218-1 Ziff. 5.6.2, 5.8.3 — Ziff. 5.7.3 fehlt)
- HP1671 covers GT 1.30 (EN 12417 — Pos. detail fehlt)

Followup: 2 fine-grained sub-paragraph references (5.7.3, Pos. 1.1.4)
can be added later as measure-text updates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-16 22:51:50 +02:00
parent 35d6422247
commit 69729ef6ac
@@ -304,6 +304,11 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
hazCat := hazardCatByID[hazID]
accepted := acceptableMeasureCategories(hazCat)
added := 0
// Aggregate norm references across all kept mitigations for this
// hazard so we can attach a single "Referenzierte Normen" line
// to the hazard description below.
var hazardNorms []string
seenNorm := map[string]bool{}
if patternMIDs, ok := hazardPatternMeasures[hazID]; ok {
for _, mid := range patternMIDs {
@@ -324,9 +329,19 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
if rt == "" {
rt = iace.ReductionTypeInformation
}
mitDesc := entry.Description
if len(entry.NormReferences) > 0 {
mitDesc += "\n\nNormen: " + strings.Join(entry.NormReferences, " | ")
for _, n := range entry.NormReferences {
if !seenNorm[n] {
seenNorm[n] = true
hazardNorms = append(hazardNorms, n)
}
}
}
_, cerr := h.store.CreateMitigation(ctx, iace.CreateMitigationRequest{
HazardID: hazID, ReductionType: rt,
Name: entry.Name, Description: entry.Description,
Name: entry.Name, Description: mitDesc,
})
if cerr != nil {
fmt.Printf("MEASURE-ERROR: mid=%s name=%s err=%v\n", mid, entry.Name, cerr)
@@ -336,6 +351,18 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
}
}
}
// Append the aggregated norm list to the hazard so the UI shows
// a single "Referenzierte Normen" panel per hazard.
if len(hazardNorms) > 0 {
if existing, getErr := h.store.GetHazard(ctx, hazID); getErr == nil && existing != nil {
if !strings.Contains(existing.Description, "Referenzierte Normen:") {
newDesc := existing.Description + "\n\nReferenzierte Normen: " + strings.Join(hazardNorms, " | ")
_, _ = h.store.UpdateHazard(ctx, hazID, map[string]interface{}{
"description": newDesc,
})
}
}
}
if added == 0 {
zeroMitigationHazards++