package iace // Sources & Licenses appendix for the IACE Tech-File PDF export. // Stufe 4 of the Attribution Renderer (Task #29). // // The IACE engine generates hazards from BreakPilot Pattern-IDs that // themselves cite ISO 12100, EN 13849, EN ISO 13855 etc. Those norm // identifiers are R3 (DIN/EN copyright — identifier-only). The // pattern-engine output itself is R3 (BreakPilot own work). OSHA values // surfaced via the minimum-distance library are R1 (US Federal PD). // // This appendix aggregates what the Tech-File ACTUALLY cited and shows // it grouped by license rule with the mandatory disclaimer that the // per-export footer cannot be replaced by a pauschal Impressum-Hinweis. import ( "sort" "strings" "github.com/jung-kurt/gofpdf" ) // pdfSourcesAppendix renders the "Quellen & Lizenzen" appendix page. // Called by ExportPDF after the regulatory classifications block. func (e *DocumentExporter) pdfSourcesAppendix(pdf *gofpdf.Fpdf, hazards []Hazard, mitigations []Mitigation) { pdf.SetFont("Helvetica", "B", 14) pdf.SetTextColor(124, 58, 237) pdf.CellFormat(0, 10, "Quellen und Lizenzen", "", 1, "L", false, 0, "") pdf.Ln(2) pdf.SetFont("Helvetica", "", 9) pdf.SetTextColor(80, 80, 80) intro := "Diese Risikobeurteilung verwendet die deterministische BreakPilot IACE " + "Pattern-Engine sowie zitierte Sicherheitsnormen. Die folgende Aufstellung " + "listet die konkret in diesem Dokument zitierten Quellen mit ihrer Lizenzregel." pdf.MultiCell(0, 5, intro, "", "L", false) pdf.Ln(3) pdf.SetFont("Helvetica", "B", 10) pdf.SetTextColor(0, 0, 0) pdf.CellFormat(0, 7, "R3 — BreakPilot Pattern-Engine (Eigenwerk, Identifier-Verweis)", "", 1, "L", false, 0, "") pdf.SetFont("Helvetica", "", 9) pdf.SetTextColor(60, 60, 60) pdf.MultiCell(0, 5, "Alle in diesem Dokument referenzierten HP-XXXX-Identifier stammen aus der "+ "BreakPilot IACE Pattern-Library (Eigenwerk). Keine externe Lizenz-Attribution "+ "erforderlich.", "", "L", false) pdf.Ln(3) norms := extractCitedNorms(hazards, mitigations) if len(norms) > 0 { pdf.SetFont("Helvetica", "B", 10) pdf.SetTextColor(0, 0, 0) pdf.CellFormat(0, 7, "R3 — Sicherheitsnormen (DIN/EN/ISO/IEC, Identifier-Verweis)", "", 1, "L", false, 0, "") pdf.SetFont("Helvetica", "", 9) pdf.SetTextColor(60, 60, 60) pdf.MultiCell(0, 5, "DIN-/EN-/ISO-/IEC-Normen unterliegen dem Urheberrecht der jeweiligen "+ "Normungsorganisation. In diesem Dokument werden Normen ausschliesslich "+ "als Identifier (Norm-Nummer und Abschnitt) zitiert; kein Volltext aus "+ "diesen Normen wurde reproduziert. Konkret zitiert:", "", "L", false) pdf.Ln(1) for _, n := range norms { pdf.CellFormat(0, 5, " • "+n, "", 1, "L", false, 0, "") } pdf.Ln(2) } pdf.SetFont("Helvetica", "B", 10) pdf.SetTextColor(0, 0, 0) pdf.CellFormat(0, 7, "R1 — Hoheitsrecht / Public Domain (woertlich uebernehmbar)", "", 1, "L", false, 0, "") pdf.SetFont("Helvetica", "", 9) pdf.SetTextColor(60, 60, 60) pdf.MultiCell(0, 5, "Soweit Werte aus US Federal Code (OSHA 29 CFR Subpart O) oder EU-Recht "+ "(Maschinenverordnung 2023/1230, AI Act 2024/1689) referenziert werden, "+ "sind diese als R1 woertlich uebernehmbar. Keine Attribution-Pflicht.", "", "L", false) pdf.Ln(4) pdf.SetFont("Helvetica", "I", 8) pdf.SetTextColor(120, 120, 120) pdf.MultiCell(0, 4, "Hinweis: Pauschalvermerke in AGB oder Impressum reichen rechtlich nicht — "+ "die werknahe Attribution erfolgt durch diese Quellenseite. Vollstaendiges "+ "Quellenverzeichnis aller im BreakPilot-System verwendeten Quellen siehe "+ "/sdk/licenses im Web-Frontend.", "", "L", false) } // extractCitedNorms scans hazard descriptions + scenario fields for // recognised norm identifiers. The detection is intentionally narrow: // only well-known prefixes (EN/ISO/IEC/DIN) and only when followed by // digits, so free-form prose is not turned into spurious citations. func extractCitedNorms(hz []Hazard, mt []Mitigation) []string { seen := make(map[string]bool) consider := func(s string) { fields := strings.FieldsFunc(s, func(r rune) bool { return r == ' ' || r == ',' || r == ';' || r == '\n' || r == ';' || r == '(' }) for i := 0; i < len(fields)-1; i++ { head := strings.ToUpper(strings.TrimSpace(fields[i])) next := strings.TrimSpace(fields[i+1]) if !(head == "EN" || head == "ISO" || head == "IEC" || head == "DIN") { continue } if next == "" { continue } // Accept "ISO 12100", "EN 13849-1", "DIN EN 60204-1" etc. if next[0] >= '0' && next[0] <= '9' { seen[head+" "+next] = true } else if head == "DIN" && (strings.HasPrefix(strings.ToUpper(next), "EN") || strings.HasPrefix(strings.ToUpper(next), "ISO")) && i+2 < len(fields) { third := strings.TrimSpace(fields[i+2]) if third != "" && third[0] >= '0' && third[0] <= '9' { seen[head+" "+next+" "+third] = true } } } } for _, h := range hz { consider(h.Description) consider(h.Scenario) consider(h.PossibleHarm) } for _, m := range mt { consider(m.Description) consider(m.Name) } out := make([]string, 0, len(seen)) for k := range seen { out = append(out, k) } sort.Strings(out) return out }