package iace import ( "fmt" "sort" "strings" ) // RenderCrossRefAppendix builds a Markdown appendix for a tech-file section // that lists the international equivalents of the given norm IDs. It is // intended to be appended to the "Applied Harmonised Standards" section so // the same tech file is usable for CE + US/CN/JP market submissions. // // Output format: // // ## Anhang: Internationale Aequivalenzen / International Cross-Reference // // Diese Tabelle ordnet die in dieser technischen Dokumentation angewandten // EU-Normen den Pendants in anderen Maerkten zu. Die Spalte "Relation" gibt // an, ob es sich um eine identische Uebernahme, eine teilweise Ueberdeckung // oder ein abgeloestes (superseded_by) Dokument handelt. Vor Nutzung im // jeweiligen Marktraum durch eine sachkundige Person verifizieren. // // | EU Norm | Region | International Identifier | Relation | Confidence | // |---------|--------|--------------------------|----------|------------| // ... // // If no norms have crossref entries, returns an empty string so the caller // can skip the appendix entirely. func RenderCrossRefAppendix(normIDs []string) string { rows := collectCrossRefRows(normIDs) if len(rows) == 0 { return "" } var b strings.Builder b.WriteString("\n\n## Anhang: Internationale Aequivalenzen / International Cross-Reference\n\n") b.WriteString("Diese Tabelle ordnet die in dieser technischen Dokumentation angewandten EU-Normen den Pendants in anderen Maerkten zu (DIN, ANSI/NFPA/UL/OSHA, GB, JIS u.a.). Die Spalte ") b.WriteString("**Relation** kennzeichnet `identical` (wortgleiche Uebernahme), `equivalent` (Kompatibilitaet auf Verfahrensebene), ") b.WriteString("`partial` (Teilueberdeckung — vor Nutzung pruefen), `supersedes`/`superseded_by` (Ablaufverhaeltnis). ") b.WriteString("Die Spalte **Confidence** drueckt die intern hinterlegte Verlaesslichkeit der Zuordnung aus. ") b.WriteString("Vor Verwendung in einem Drittmarkt durch eine sachkundige Person verifizieren.\n\n") b.WriteString("| EU Norm (verwendet) | Region | International Identifier | Relation | Confidence | Hinweis |\n") b.WriteString("|---------------------|--------|--------------------------|----------|------------|---------|\n") for _, row := range rows { note := row.Notes if note == "" { note = "—" } // Escape pipes in identifier and note for markdown table safety. fmt.Fprintf(&b, "| %s | %s | %s | %s | %s | %s |\n", escapeCell(row.SourceNorm), escapeCell(row.Region), escapeCell(row.Identifier), escapeCell(row.Relation), escapeCell(row.Confidence), escapeCell(note), ) } b.WriteString("\n*Quelle: BreakPilot Cross-Reference Matrix. Keine Originalnormtexte reproduziert — nur Identifikatoren. Stand: Bezugsperiode der jeweiligen Norm-Bibliothek.*\n") return b.String() } // crossRefRow is a flattened row of the matrix used by the renderer. type crossRefRow struct { SourceNorm string Region string Identifier string Relation string Confidence string Notes string } // collectCrossRefRows expands the per-norm mapping list into a sorted slice // of rows. Sort order: source norm ID first, then region in a canonical // regional order so EU markets appear before non-EU. func collectCrossRefRows(normIDs []string) []crossRefRow { regionRank := map[string]int{ "EU-DIN": 0, "INTL-ISO": 1, "US-ANSI": 2, "US-NFPA": 3, "US-UL": 4, "US-OSHA": 5, "US-ASME": 6, "US-ASTM": 7, "US-SAE": 8, "US-NIOSH": 9, "US-FDA": 10, "US-EPA": 11, "US-NEMA": 12, "US-NSF": 13, "US-API": 14, "US-CPSC": 15, "US-AHRI": 16, "US-ASHRAE": 17, "US-FCC": 18, "US-DOT": 19, "US-MSHA": 20, "US-FM": 21, "US-AAR": 22, "US-ACI": 23, "US-ADA": 24, "US-AAMA": 25, "US-APA": 26, "US-APSP": 27, "US-EJMA": 28, "US-ICC": 29, "US-SMACNA": 30, "CN-GB": 40, "JP-JIS": 50, } seen := make(map[string]bool) var rows []crossRefRow for _, id := range normIDs { if seen[id] { continue } seen[id] = true cr := GetNormCrossRef(id) for _, m := range cr.Mappings { rows = append(rows, crossRefRow{ SourceNorm: id, Region: m.Region, Identifier: m.Identifier, Relation: m.Relation, Confidence: m.Confidence, Notes: m.Notes, }) } } sort.SliceStable(rows, func(i, j int) bool { if rows[i].SourceNorm != rows[j].SourceNorm { return rows[i].SourceNorm < rows[j].SourceNorm } ri, ok := regionRank[rows[i].Region] if !ok { ri = 99 } rj, ok := regionRank[rows[j].Region] if !ok { rj = 99 } return ri < rj }) return rows } // escapeCell escapes pipes and newlines so a Markdown table cell does not break. func escapeCell(s string) string { s = strings.ReplaceAll(s, "|", "\\|") s = strings.ReplaceAll(s, "\n", " ") return s }