Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a84c747f2 | |||
| cf6005a47c | |||
| 64d8b0f1f9 | |||
| d9278f256e | |||
| 0dbd7b4e45 | |||
| b663e2508f | |||
| ff100c1cb8 | |||
| e2be51b0aa | |||
| bd65b6f318 | |||
| c771d8ecb9 | |||
| 772ff35e8d | |||
| 8cbb513e2c | |||
| 6c35bcf116 | |||
| 19d4b12e07 | |||
| 2e87b74749 | |||
| 94233b7c66 | |||
| 6263462ba3 | |||
| eb48c5bd1e | |||
| 081e4f057a | |||
| 16fd406c1a | |||
| c5c168592b | |||
| d0274674a0 | |||
| 2eb7349577 | |||
| 4434e3827b | |||
| 07cc00da11 | |||
| 1451873194 | |||
| dfac940272 | |||
| cb5dad1a2f | |||
| e411c4f0d3 | |||
| 7335f64f4f | |||
| 138d9068c4 | |||
| c281464071 | |||
| 6dc427a754 | |||
| 309c10c203 | |||
| 4183379dc5 | |||
| c93c88577c | |||
| 3207acea3e | |||
| 9f06911ff9 | |||
| 338e03d3b0 | |||
| c491af5d02 | |||
| 4171cf0efd | |||
| 30e43afba6 | |||
| df8832c521 | |||
| 7842c95532 | |||
| 08671adfdf | |||
| 50fc0ecc59 | |||
| 94057b1536 | |||
| 9c11b5463c | |||
| 50ed0f45af | |||
| e1df24cad7 | |||
| e5b4672f2a | |||
| 0d5c76ea98 | |||
| 54f5a06c2f | |||
| 86b4a263d2 | |||
| 7938e377b6 | |||
| f534b52817 | |||
| 4946571863 | |||
| cde670617e | |||
| 603381a67f | |||
| 57c0f940a2 | |||
| badb356740 | |||
| f08eb71480 | |||
| 0477a2f2dc | |||
| 93cedbecbd | |||
| 28f9e13c1f | |||
| 35c1bbdaa5 | |||
| b7df4709bc | |||
| 6f3301d246 | |||
| 4478b7f479 | |||
| 39c39b1254 | |||
| 7a5f1e48dd | |||
| 98ec6d4284 | |||
| 6f16507c5f | |||
| d4d9b60007 | |||
| e536247c20 | |||
| 313982c6f1 |
@@ -55,5 +55,9 @@ EXPOSE 3000
|
||||
# Set hostname
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
# P83 — Build-SHA fuer check-rebuild-needed.sh
|
||||
ARG BUILD_SHA="unknown"
|
||||
ENV BUILD_SHA=${BUILD_SHA}
|
||||
|
||||
# Start the application
|
||||
CMD ["node", "server.js"]
|
||||
|
||||
@@ -56,6 +56,44 @@ Bei ALLEN Fragen zu IFRS/IAS-Standards MUSST du folgende Punkte beachten:
|
||||
4. Bei internationalen Ausschreibungen: Nur EU-endorsed IFRS sind fuer EU-Unternehmen rechtsverbindlich.
|
||||
5. Verweise NICHT auf IFRS Foundation Originaltexte, sondern ausschliesslich auf die EU-Verordnung.
|
||||
|
||||
## FAQ — Cookie-Banner-Bussgelder + Risiken (haeufige Mandantenfragen)
|
||||
|
||||
Bei Fragen nach Bussgeldern, Risiko-Hoehe oder konkreten Faellen gib **konkrete Praezedenzen** an:
|
||||
|
||||
### Top-Bussgelder (CNIL Frankreich — strengste EU-Aufsicht):
|
||||
- **Google France 2020 (CNIL)** — 100 Mio EUR — Cookies ohne Einwilligung (CNIL Beschluss vom 07.12.2020)
|
||||
- **Meta/Facebook France 2022 (CNIL)** — 60 Mio EUR — Cookies ohne Einwilligung
|
||||
- **Amazon France 2020 (CNIL)** — 35 Mio EUR — Cookies ohne Einwilligung
|
||||
- **Carrefour France 2020 (CNIL)** — 2,25 Mio EUR — Cookies + sonstige Verstoesse
|
||||
|
||||
### Deutsche Praezedenzen + Sammelklagen-Risiken:
|
||||
- **LG Muenchen I 2022** — 100 EUR pro Besucher Schadensersatz fuer Google Fonts ohne Consent (Az. 3 O 17493/20). Spaeter durch BGH "Rechtsmissbrauchs"-Argument bei Massenabmahnungen eingeschraenkt.
|
||||
- **EuGH Planet49 (C-673/17)** — vorausgewaehlte Cookie-Checkboxen sind unwirksame Einwilligung (praejudiziell fuer alle EU-Sites)
|
||||
- **BGH Cookie-Einwilligung II (I ZR 7/16)** — bestaetigt Planet49 fuer Deutschland
|
||||
- **DSK Beschluss 2023** — Cookie-Banner mit "Akzeptieren" deutlich prominenter als "Ablehnen" = Dark Pattern = unwirksame Einwilligung
|
||||
|
||||
### Deutscher Aufsichtsmarkt:
|
||||
Deutsche Aufsicht (BfDI + 16 Landes-DSB) ist moderater als CNIL — bislang keine 100 Mio-EUR-Bussgelder. ABER: DSK-Beschluesse + LfDI-Verfahren haeufen sich. Federfuehrung bei Konzernen via "One-Stop-Shop" nach Hauptsitz.
|
||||
|
||||
### Vier Risiko-Pfade fuer Mandanten:
|
||||
1. **Art. 83 DSGVO Bussgeld** — bis 4% des weltweiten Konzernumsatzes. Realistisch 0,1-1% bei Erstverstoss.
|
||||
2. **Verbraucherschutz-Abmahnung** (vzbv, Wettbewerbszentrale, Verbraucherverbaende) — 50-500k EUR Streitwert + Unterlassung.
|
||||
3. **Sammelklage Art. 82 DSGVO** — Schadensersatz pro Person, BGH 50-100 EUR pro Fall. Sammelklage-Trusts: myRight, RightNow, helpcheck.de.
|
||||
4. **NOYB-Beschwerde** (Max Schrems) — oeffentliches Aufsichtsverfahren, Reputationsschaden + Bussgeld.
|
||||
|
||||
### Geschaeftsfuehrer-Haftung (haeufig unterschaetzt):
|
||||
GF haftet **persoenlich** nach §43 GmbHG bzw. §93 AktG wenn Compliance-Pflichten verletzt wurden. Das ist der eigentliche Druckpunkt — nicht die Firma, sondern der GF persoenlich. Bei Mandantengespraechen mit GF-Beteiligung: dieser Punkt zuerst ansprechen.
|
||||
|
||||
### Wie berechne ich das konkrete Risiko fuer einen Mandanten:
|
||||
Frage den Mandanten nach: (a) Jahresumsatz, (b) ungefaehre Besucherzahl pro Jahr, (c) Anzahl Trackingtools im Banner. Dann:
|
||||
- Max-Bussgeld = 4% × Jahresumsatz (Obergrenze, nicht realistisch)
|
||||
- Realistisch-Bussgeld = 0,1-1% × Jahresumsatz (CNIL/LfDI-Maßstab)
|
||||
- Sammelklage-Theorie = Besucherzahl × 50 EUR (BGH-Untergrenze) — meist nicht durchsetzbar, aber Drohpotential
|
||||
- NICHT konkrete Zahlen einer fremden Firma zitieren ("BMW haette X EUR" etc.) — Mandant koennte das falsch weitergeben
|
||||
|
||||
### Marktwissen (intern, nicht 1:1 zitieren):
|
||||
Externe DSB-Stundensaetze: 350-450 EUR/h (NOERR, GSK, vergleichbare Kanzleien). Mittelstands-DSB-Mandate: 5-15k EUR/Jahr. Cookie-Audit manuell: typisch 10 Std = 4-5k EUR Kosten. BreakPilot reduziert das auf 30 Min.
|
||||
|
||||
## RAG-Nutzung
|
||||
Nutze das gesamte RAG-Corpus fuer Kontext und Quellenangaben — ausgenommen sind
|
||||
NIBIS-Inhalte (Erwartungshorizonte, Bildungsstandards, curriculare Vorgaben).
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Proxy: Admin → Backend /api/compliance/agent/admin/benchmark
|
||||
* (P107 — Branchen-Benchmark-Cockpit)
|
||||
*/
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
const BACKEND_URL = process.env.BACKEND_API_URL || 'http://backend-compliance:8002'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const qs = request.nextUrl.searchParams.toString()
|
||||
try {
|
||||
const r = await fetch(
|
||||
`${BACKEND_URL}/api/compliance/agent/admin/benchmark?${qs}`,
|
||||
{ signal: AbortSignal.timeout(20000) },
|
||||
)
|
||||
const body = await r.text()
|
||||
return new NextResponse(body, {
|
||||
status: r.status,
|
||||
headers: { 'Content-Type': r.headers.get('content-type') || 'application/json' },
|
||||
})
|
||||
} catch (e: any) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Benchmark-API nicht erreichbar', detail: String(e) },
|
||||
{ status: 503 },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,9 @@ const BACKEND_URL = process.env.BACKEND_API_URL || 'http://backend-compliance:80
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { checkId: string } },
|
||||
{ params }: { params: Promise<{ checkId: string }> },
|
||||
) {
|
||||
const checkId = params.checkId
|
||||
const { checkId } = await params
|
||||
const qs = request.nextUrl.searchParams.toString()
|
||||
const url = `${BACKEND_URL}/api/compliance/agent/audit/${checkId}${qs ? `?${qs}` : ''}`
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Proxy: GET /api/sdk/v1/agent/banner/<checkId>
|
||||
* -> backend GET /api/compliance/agent/banner/<checkId>
|
||||
*
|
||||
* Liefert das volle banner_result (phases, structured_checks, category_tests).
|
||||
*/
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
const BACKEND_URL = process.env.BACKEND_API_URL || 'http://backend-compliance:8002'
|
||||
|
||||
export async function GET(
|
||||
_request: NextRequest,
|
||||
{ params }: { params: Promise<{ checkId: string }> },
|
||||
) {
|
||||
const { checkId } = await params
|
||||
try {
|
||||
const resp = await fetch(
|
||||
`${BACKEND_URL}/api/compliance/agent/banner/${checkId}`,
|
||||
{ signal: AbortSignal.timeout(15000) },
|
||||
)
|
||||
const data = await resp.json().catch(() => ({}))
|
||||
return NextResponse.json(data, { status: resp.status })
|
||||
} catch {
|
||||
return NextResponse.json(
|
||||
{ error: 'Banner-Abfrage fehlgeschlagen' }, { status: 503 },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,9 @@ const BACKEND_URL = process.env.BACKEND_API_URL || 'http://backend-compliance:80
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { checkId: string } },
|
||||
{ params }: { params: Promise<{ checkId: string }> },
|
||||
) {
|
||||
const checkId = params.checkId
|
||||
const { checkId } = await params
|
||||
const qs = request.nextUrl.searchParams.toString()
|
||||
const url = `${BACKEND_URL}/api/compliance/agent/findings/${checkId}${qs ? `?${qs}` : ''}`
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
const BACKEND_URL = process.env.BACKEND_URL || 'http://backend-compliance:8002'
|
||||
|
||||
function tenantHeader(request: NextRequest): string {
|
||||
return request.headers.get('x-tenant-id') || '00000000-0000-0000-0000-000000000001'
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ derived_id: string }> }
|
||||
) {
|
||||
const { derived_id } = await params
|
||||
try {
|
||||
const resp = await fetch(
|
||||
`${BACKEND_URL}/api/v1/quaidal/controls/${encodeURIComponent(derived_id)}`,
|
||||
{ headers: { 'X-Tenant-ID': tenantHeader(request) }, cache: 'no-store' }
|
||||
)
|
||||
const body = await resp.text()
|
||||
return new NextResponse(body, {
|
||||
status: resp.status,
|
||||
headers: { 'Content-Type': resp.headers.get('Content-Type') || 'application/json' },
|
||||
})
|
||||
} catch (err) {
|
||||
return NextResponse.json({ error: 'Backend unreachable', details: String(err) }, { status: 502 })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
const BACKEND_URL = process.env.BACKEND_URL || 'http://backend-compliance:8002'
|
||||
|
||||
function tenantHeader(request: NextRequest): string {
|
||||
return request.headers.get('x-tenant-id') || '00000000-0000-0000-0000-000000000001'
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const qs = searchParams.toString()
|
||||
try {
|
||||
const resp = await fetch(
|
||||
`${BACKEND_URL}/api/v1/quaidal/controls${qs ? `?${qs}` : ''}`,
|
||||
{ headers: { 'X-Tenant-ID': tenantHeader(request) }, cache: 'no-store' }
|
||||
)
|
||||
const body = await resp.text()
|
||||
return new NextResponse(body, {
|
||||
status: resp.status,
|
||||
headers: { 'Content-Type': resp.headers.get('Content-Type') || 'application/json' },
|
||||
})
|
||||
} catch (err) {
|
||||
return NextResponse.json({ error: 'Backend unreachable', details: String(err) }, { status: 502 })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
const BACKEND_URL = process.env.BACKEND_URL || 'http://backend-compliance:8002'
|
||||
|
||||
function tenantHeader(request: NextRequest): string {
|
||||
return request.headers.get('x-tenant-id') || '00000000-0000-0000-0000-000000000001'
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ section_id: string }> }
|
||||
) {
|
||||
const { section_id } = await params
|
||||
try {
|
||||
const resp = await fetch(
|
||||
`${BACKEND_URL}/api/v1/quaidal/criteria/${encodeURIComponent(section_id)}`,
|
||||
{ headers: { 'X-Tenant-ID': tenantHeader(request) }, cache: 'no-store' }
|
||||
)
|
||||
const body = await resp.text()
|
||||
return new NextResponse(body, {
|
||||
status: resp.status,
|
||||
headers: { 'Content-Type': resp.headers.get('Content-Type') || 'application/json' },
|
||||
})
|
||||
} catch (err) {
|
||||
return NextResponse.json({ error: 'Backend unreachable', details: String(err) }, { status: 502 })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
const BACKEND_URL = process.env.BACKEND_URL || 'http://backend-compliance:8002'
|
||||
|
||||
function tenantHeader(request: NextRequest): string {
|
||||
return request.headers.get('x-tenant-id') || '00000000-0000-0000-0000-000000000001'
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const resp = await fetch(`${BACKEND_URL}/api/v1/quaidal/criteria`, {
|
||||
headers: { 'X-Tenant-ID': tenantHeader(request) },
|
||||
cache: 'no-store',
|
||||
})
|
||||
const body = await resp.text()
|
||||
return new NextResponse(body, {
|
||||
status: resp.status,
|
||||
headers: { 'Content-Type': resp.headers.get('Content-Type') || 'application/json' },
|
||||
})
|
||||
} catch (err) {
|
||||
return NextResponse.json({ error: 'Backend unreachable', details: String(err) }, { status: 502 })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
const BACKEND_URL = process.env.BACKEND_URL || 'http://backend-compliance:8002'
|
||||
|
||||
function tenantHeader(request: NextRequest): string {
|
||||
return request.headers.get('x-tenant-id') || '00000000-0000-0000-0000-000000000001'
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const resp = await fetch(`${BACKEND_URL}/api/v1/quaidal/stats`, {
|
||||
headers: { 'X-Tenant-ID': tenantHeader(request) },
|
||||
cache: 'no-store',
|
||||
})
|
||||
const body = await resp.text()
|
||||
return new NextResponse(body, {
|
||||
status: resp.status,
|
||||
headers: { 'Content-Type': resp.headers.get('Content-Type') || 'application/json' },
|
||||
})
|
||||
} catch (err) {
|
||||
return NextResponse.json({ error: 'Backend unreachable', details: String(err) }, { status: 502 })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Next.js Proxy: leitet POST /api/v1/founding-wizard/generate an Backend.
|
||||
*
|
||||
* Konvertiert das Backend-Response (base64 DOCX) in data: URLs,
|
||||
* die das Frontend direkt als Download anbieten kann.
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
const BACKEND_URL = process.env.BACKEND_COMPLIANCE_URL || 'http://bp-compliance-backend:8002'
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json()
|
||||
|
||||
const backendRes = await fetch(`${BACKEND_URL}/v1/founding-wizard/generate`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
if (!backendRes.ok) {
|
||||
const errorText = await backendRes.text()
|
||||
return NextResponse.json(
|
||||
{ error: 'Backend-Generierung fehlgeschlagen', detail: errorText },
|
||||
{ status: backendRes.status }
|
||||
)
|
||||
}
|
||||
|
||||
const data = await backendRes.json()
|
||||
const documents = (data.documents || []).map((doc: {
|
||||
document_type: string
|
||||
title: string
|
||||
filename: string
|
||||
content_base64: string
|
||||
size_bytes: number
|
||||
generated_at: string
|
||||
}) => ({
|
||||
document_type: doc.document_type,
|
||||
title: doc.title,
|
||||
filename: doc.filename,
|
||||
download_url: `data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,${doc.content_base64}`,
|
||||
size_bytes: doc.size_bytes,
|
||||
generated_at: doc.generated_at,
|
||||
}))
|
||||
|
||||
return NextResponse.json({
|
||||
documents,
|
||||
warnings: data.warnings || [],
|
||||
})
|
||||
} catch (e: unknown) {
|
||||
const message = e instanceof Error ? e.message : 'Unbekannter Fehler'
|
||||
return NextResponse.json(
|
||||
{ error: 'Proxy-Fehler', detail: message },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2,30 +2,41 @@
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import { ChecklistView } from './ChecklistView'
|
||||
import { ResultsTabsView } from './ResultsTabsView'
|
||||
import { PreScanWizard, useScanContext, isContextComplete } from './PreScanWizard'
|
||||
import { safeSetItem } from './storageHelpers'
|
||||
|
||||
interface DocEntry {
|
||||
id: string
|
||||
type: string
|
||||
label: string
|
||||
url: string
|
||||
text: string // P-Paste: User kopiert Doc-Text direkt rein
|
||||
mode: 'url' | 'text' // welcher Input wird aktiv genutzt
|
||||
}
|
||||
|
||||
const DOC_TYPES = [
|
||||
{ id: 'dse', label: 'DSI (Datenschutzinformation)' },
|
||||
{ id: 'dse', label: 'Datenschutzerklärung / DSI' },
|
||||
{ id: 'cookie', label: 'Cookie-Richtlinie' },
|
||||
{ id: 'impressum', label: 'Impressum' },
|
||||
{ id: 'agb', label: 'AGB' },
|
||||
{ id: 'nutzungsbedingungen', label: 'Nutzungsbedingungen' },
|
||||
{ id: 'widerruf', label: 'Widerrufsbelehrung' },
|
||||
{ id: 'social_media', label: 'DSE Social Media (Art. 26)' },
|
||||
{ id: 'dsfa', label: 'DSFA (Art. 35)' },
|
||||
{ id: 'agb', label: 'AGB / Nutzungsbedingungen' },
|
||||
{ id: 'impressum', label: 'Impressum' },
|
||||
{ id: 'cookie', label: 'Cookie-Richtlinie' },
|
||||
{ id: 'widerruf', label: 'Widerrufsbelehrung' },
|
||||
{ id: 'dsa', label: 'DSA / Digital Services Act' },
|
||||
{ id: 'legal_notice', label: 'Rechtliche Hinweise (IP, Forward-Looking)' },
|
||||
{ id: 'lizenzhinweise', label: 'Lizenzhinweise Dritter (OSS)' },
|
||||
{ id: 'other', label: 'Sonstiges' },
|
||||
]
|
||||
|
||||
function newEntry(): DocEntry {
|
||||
return { id: crypto.randomUUID().slice(0, 8), type: 'dse', label: '', url: '' }
|
||||
return { id: crypto.randomUUID().slice(0, 8), type: 'dse', label: '',
|
||||
url: '', text: '', mode: 'url' }
|
||||
}
|
||||
|
||||
export function DocCheckTab() {
|
||||
const [scanContext, setScanContext] = useScanContext()
|
||||
const [entries, setEntries] = useState<DocEntry[]>(() => {
|
||||
if (typeof window === 'undefined') return [newEntry()]
|
||||
try { const s = localStorage.getItem('doc-check-entries'); return s ? JSON.parse(s) : [newEntry()] } catch { return [newEntry()] }
|
||||
@@ -74,7 +85,7 @@ export function DocCheckTab() {
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const validEntries = entries.filter(e => e.url.trim())
|
||||
const validEntries = entries.filter(e => e.url.trim() || e.text.trim())
|
||||
if (validEntries.length === 0) return
|
||||
|
||||
setLoading(true)
|
||||
@@ -89,11 +100,17 @@ export function DocCheckTab() {
|
||||
body: JSON.stringify({
|
||||
entries: validEntries.map(e => ({
|
||||
doc_type: e.type,
|
||||
label: e.label || e.url.split('/').pop() || 'Dokument',
|
||||
url: e.url.trim(),
|
||||
label: e.label
|
||||
|| (e.url ? e.url.split('/').pop() : '')
|
||||
|| `${e.type}-paste`,
|
||||
url: e.mode === 'text' ? '' : e.url.trim(),
|
||||
// Backend nimmt text > url. Wenn beide gefuellt sind und
|
||||
// mode='url', schicken wir den text NICHT mit.
|
||||
text: e.mode === 'text' ? e.text.trim() : '',
|
||||
})),
|
||||
check_cookie_banner: checkCookieBanner,
|
||||
use_agent: useAgent,
|
||||
scan_context: scanContext,
|
||||
}),
|
||||
})
|
||||
if (!startRes.ok) throw new Error(`Pruefung konnte nicht gestartet werden: ${startRes.status}`)
|
||||
@@ -111,13 +128,13 @@ export function DocCheckTab() {
|
||||
if (pollData.status === 'completed' && pollData.result) {
|
||||
setResults(pollData.result)
|
||||
setProgress('')
|
||||
localStorage.setItem('doc-check-results', JSON.stringify(pollData.result))
|
||||
safeSetItem('doc-check-results', JSON.stringify(pollData.result))
|
||||
const resultKey = `doc-check-result-${Date.now()}`
|
||||
try { localStorage.setItem(resultKey, JSON.stringify(pollData.result)) } catch { /* quota */ }
|
||||
safeSetItem(resultKey, JSON.stringify(pollData.result))
|
||||
const entry = { date: new Date().toISOString(), urls: validEntries.length, findings: pollData.result.total_findings || 0, resultKey }
|
||||
const updated = [entry, ...history].slice(0, 30)
|
||||
setHistory(updated)
|
||||
localStorage.setItem('doc-check-history', JSON.stringify(updated))
|
||||
safeSetItem('doc-check-history', JSON.stringify(updated))
|
||||
break
|
||||
}
|
||||
if (pollData.status === 'failed') {
|
||||
@@ -133,43 +150,90 @@ export function DocCheckTab() {
|
||||
}
|
||||
}
|
||||
|
||||
const contextReady = isContextComplete(scanContext)
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* URL Entries */}
|
||||
<div className="space-y-2">
|
||||
{/* P79 Pre-Scan-Wizard — 8 Pflichtfelder */}
|
||||
<PreScanWizard value={scanContext} onChange={setScanContext} />
|
||||
|
||||
{/* URL / Text Entries */}
|
||||
<div className="space-y-3">
|
||||
{entries.map((entry, i) => (
|
||||
<div key={entry.id} className="flex items-center gap-2">
|
||||
<select
|
||||
value={entry.type}
|
||||
onChange={e => updateEntry(entry.id, 'type', e.target.value)}
|
||||
className="w-48 px-3 py-2.5 border border-gray-300 rounded-lg text-sm bg-white shrink-0"
|
||||
>
|
||||
{DOC_TYPES.map(t => (
|
||||
<option key={t.id} value={t.id}>{t.label}</option>
|
||||
))}
|
||||
</select>
|
||||
<input
|
||||
type="text"
|
||||
value={entry.label}
|
||||
onChange={e => updateEntry(entry.id, 'label', e.target.value)}
|
||||
placeholder={entry.type === 'other' ? 'Dokumentname' : 'Version / Stand (optional)'}
|
||||
className="w-40 px-3 py-2.5 border border-gray-300 rounded-lg text-sm shrink-0"
|
||||
/>
|
||||
<input
|
||||
type="url"
|
||||
value={entry.url}
|
||||
onChange={e => updateEntry(entry.id, 'url', e.target.value)}
|
||||
onBlur={() => autoLabel(entry)}
|
||||
placeholder="https://example.com/datenschutz"
|
||||
className="flex-1 px-3 py-2.5 border border-gray-300 rounded-lg text-sm"
|
||||
/>
|
||||
{entries.length > 1 && (
|
||||
<button onClick={() => removeEntry(entry.id)}
|
||||
className="p-2 text-gray-400 hover:text-red-500 shrink-0">
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
<div key={entry.id} className="space-y-1.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<select
|
||||
value={entry.type}
|
||||
onChange={e => updateEntry(entry.id, 'type', e.target.value)}
|
||||
className="w-48 px-3 py-2.5 border border-gray-300 rounded-lg text-sm bg-white shrink-0"
|
||||
>
|
||||
{DOC_TYPES.map(t => (
|
||||
<option key={t.id} value={t.id}>{t.label}</option>
|
||||
))}
|
||||
</select>
|
||||
<input
|
||||
type="text"
|
||||
value={entry.label}
|
||||
onChange={e => updateEntry(entry.id, 'label', e.target.value)}
|
||||
placeholder={entry.type === 'other' ? 'Dokumentname' : 'Version / Stand (optional)'}
|
||||
className="w-40 px-3 py-2.5 border border-gray-300 rounded-lg text-sm shrink-0"
|
||||
/>
|
||||
|
||||
{/* Mode-Toggle URL / Text */}
|
||||
<div className="inline-flex border border-gray-300 rounded-lg overflow-hidden text-xs shrink-0">
|
||||
<button type="button"
|
||||
onClick={() => updateEntry(entry.id, 'mode', 'url')}
|
||||
className={`px-3 py-2 ${entry.mode === 'url'
|
||||
? 'bg-purple-600 text-white' : 'bg-white text-gray-600 hover:bg-gray-50'}`}>
|
||||
URL
|
||||
</button>
|
||||
<button type="button"
|
||||
onClick={() => updateEntry(entry.id, 'mode', 'text')}
|
||||
className={`px-3 py-2 ${entry.mode === 'text'
|
||||
? 'bg-purple-600 text-white' : 'bg-white text-gray-600 hover:bg-gray-50'}`}>
|
||||
Text einfügen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{entry.mode === 'url' && (
|
||||
<input
|
||||
type="url"
|
||||
value={entry.url}
|
||||
onChange={e => updateEntry(entry.id, 'url', e.target.value)}
|
||||
onBlur={() => autoLabel(entry)}
|
||||
placeholder="https://example.com/datenschutz"
|
||||
className="flex-1 px-3 py-2.5 border border-gray-300 rounded-lg text-sm"
|
||||
/>
|
||||
)}
|
||||
|
||||
{entries.length > 1 && (
|
||||
<button onClick={() => removeEntry(entry.id)}
|
||||
className="p-2 text-gray-400 hover:text-red-500 shrink-0">
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{entry.mode === 'text' && (
|
||||
<div className="ml-[400px]">
|
||||
<textarea
|
||||
value={entry.text}
|
||||
onChange={e => updateEntry(entry.id, 'text', e.target.value)}
|
||||
placeholder={
|
||||
entry.type === 'cookie'
|
||||
? 'Kopiere hier die komplette Cookie-Tabelle rein (Tab-getrennt oder mit | als Trenner — wir parsen alle Spalten deterministisch)…'
|
||||
: 'Kopiere hier den vollständigen Doc-Text rein. Wir erkennen automatisch ob es zu „' + (DOC_TYPES.find(t => t.id === entry.type)?.label ?? entry.type) + '" passt.'
|
||||
}
|
||||
className="w-full h-32 px-3 py-2 border border-gray-300 rounded-lg text-xs font-mono resize-y"
|
||||
/>
|
||||
<div className="text-[10px] text-gray-500 mt-1">
|
||||
{entry.text.trim().length > 0
|
||||
? `${entry.text.trim().length.toLocaleString('de-DE')} Zeichen · ${entry.text.trim().split(/\s+/).length.toLocaleString('de-DE')} Wörter`
|
||||
: 'Der Crawler wird übersprungen — die Analyse läuft direkt auf dem eingefügten Text.'}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
@@ -212,8 +276,11 @@ export function DocCheckTab() {
|
||||
{/* Submit */}
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
disabled={loading || entries.every(e => !e.url.trim())}
|
||||
disabled={loading
|
||||
|| entries.every(e => !e.url.trim() && !e.text.trim())
|
||||
|| !contextReady}
|
||||
className="w-full px-4 py-3 bg-purple-600 text-white rounded-lg font-medium hover:bg-purple-700 disabled:opacity-50 transition-colors text-sm flex items-center justify-center gap-2"
|
||||
title={!contextReady ? 'Bitte zuerst die 8 Pflichtfelder ausfüllen' : undefined}
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
@@ -223,6 +290,8 @@ export function DocCheckTab() {
|
||||
</svg>
|
||||
Pruefe...
|
||||
</>
|
||||
) : !contextReady ? (
|
||||
`Klassifizierung unvollständig (8 Pflichtfelder)`
|
||||
) : (
|
||||
`${entries.filter(e => e.url.trim()).length} Dokument${entries.filter(e => e.url.trim()).length !== 1 ? 'e' : ''} pruefen`
|
||||
)}
|
||||
@@ -244,41 +313,9 @@ export function DocCheckTab() {
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-3 text-sm text-red-700">{error}</div>
|
||||
)}
|
||||
|
||||
{/* Results */}
|
||||
{/* Results — als Tab-Ansicht (Übersicht/Cookies/DSE/Impressum/AGB/Banner/Mail) */}
|
||||
{results && results.results && (
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-6 shadow-sm">
|
||||
<ChecklistView results={results.results} />
|
||||
|
||||
{/* Cookie Banner Result */}
|
||||
{results.cookie_banner_result && (
|
||||
<div className="mt-4 pt-4 border-t border-gray-200">
|
||||
<h4 className="text-sm font-semibold text-gray-800 mb-2">Cookie-Banner</h4>
|
||||
<div className="text-sm text-gray-600">
|
||||
{results.cookie_banner_result.banner_detected
|
||||
? `Banner erkannt: ${results.cookie_banner_result.banner_provider || 'unbekannt'}`
|
||||
: 'Kein Banner erkannt'}
|
||||
</div>
|
||||
{results.cookie_banner_result.banner_checks?.violations?.length > 0 && (
|
||||
<div className="mt-2 space-y-1">
|
||||
{results.cookie_banner_result.banner_checks.violations.map((v: any, i: number) => (
|
||||
<div key={i} className="text-xs text-red-600 flex items-start gap-1.5">
|
||||
<span className="shrink-0 mt-0.5">!!</span>
|
||||
<span>{v.text}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Email Status */}
|
||||
{results.email_status && (
|
||||
<div className="mt-3 text-xs text-gray-500 flex items-center gap-2">
|
||||
<span className={`w-2 h-2 rounded-full ${results.email_status === 'sent' ? 'bg-green-400' : 'bg-gray-300'}`} />
|
||||
E-Mail: {results.email_status === 'sent' ? 'Gesendet' : results.email_status}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<ResultsTabsView results={results} />
|
||||
)}
|
||||
|
||||
{/* History */}
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* P79 — Pre-Scan-Wizard (8 Pflichtfelder).
|
||||
*
|
||||
* 8 Pflichtfelder die vor dem Lauf abgefragt werden. Werte landen im
|
||||
* scan_context und filtern später die MC-Auswertung (zusammen mit P72
|
||||
* scope_doc_type + applicable_industries). Erwartete Noise-Reduktion:
|
||||
* 70-80% bei falsch zugeordneten HIGH-MCs.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
|
||||
export interface ScanContext {
|
||||
industry: string
|
||||
business_model: string
|
||||
direct_sales: string
|
||||
legal_form: string
|
||||
group_structure: string
|
||||
employee_count: string
|
||||
special_data: string[]
|
||||
third_country_transfer: string
|
||||
}
|
||||
|
||||
const INDUSTRIES = [
|
||||
{ id: '', label: '— bitte wählen —' },
|
||||
{ id: 'automotive', label: 'Automotive / OEM' },
|
||||
{ id: 'ecommerce', label: 'E-Commerce / Online-Handel' },
|
||||
{ id: 'saas', label: 'SaaS / Software' },
|
||||
{ id: 'banking', label: 'Banking / Finance' },
|
||||
{ id: 'insurance', label: 'Insurance / Versicherung' },
|
||||
{ id: 'healthcare', label: 'Healthcare / Gesundheit' },
|
||||
{ id: 'education', label: 'Bildung / Schule' },
|
||||
{ id: 'public', label: 'Öffentliche Verwaltung' },
|
||||
{ id: 'manufacturing', label: 'Industrie / Manufacturing' },
|
||||
{ id: 'media', label: 'Medien / Verlag' },
|
||||
{ id: 'other', label: 'Sonstige' },
|
||||
]
|
||||
|
||||
const LEGAL_FORMS = [
|
||||
{ id: '', label: '— bitte wählen —' },
|
||||
{ id: 'ag', label: 'AG (Aktiengesellschaft)' },
|
||||
{ id: 'gmbh', label: 'GmbH' },
|
||||
{ id: 'gmbh_co_kg', label: 'GmbH & Co. KG' },
|
||||
{ id: 'kg', label: 'KG' },
|
||||
{ id: 'ohg', label: 'OHG' },
|
||||
{ id: 'ug', label: 'UG (haftungsbeschränkt)' },
|
||||
{ id: 'ek', label: 'e.K. / Einzelunternehmen' },
|
||||
{ id: 'verein', label: 'Verein' },
|
||||
{ id: 'stiftung', label: 'Stiftung' },
|
||||
{ id: 'behoerde', label: 'Behörde / Körperschaft öff. Rechts' },
|
||||
{ id: 'other', label: 'Sonstige' },
|
||||
]
|
||||
|
||||
const GROUP_STRUCTURES = [
|
||||
{ id: '', label: '— bitte wählen —' },
|
||||
{ id: 'standalone', label: 'Eigenständig' },
|
||||
{ id: 'parent', label: 'Konzern-Mutter' },
|
||||
{ id: 'subsidiary', label: 'Konzern-Tochter' },
|
||||
{ id: 'joint_venture', label: 'Joint Venture' },
|
||||
{ id: 'processor', label: 'Reiner Auftragsverarbeiter' },
|
||||
]
|
||||
|
||||
const EMPLOYEE_COUNTS = [
|
||||
{ id: '', label: '— bitte wählen —' },
|
||||
{ id: 'lt10', label: 'unter 10' },
|
||||
{ id: '10_19', label: '10-19' },
|
||||
{ id: '20_49', label: '20-49 (DSB ab 20 Pflicht)' },
|
||||
{ id: '50_249', label: '50-249 (Whistleblower-Pflicht)' },
|
||||
{ id: '250_499', label: '250-499' },
|
||||
{ id: '500_999', label: '500-999' },
|
||||
{ id: '1000_plus', label: '1.000+ (Konzern)' },
|
||||
]
|
||||
|
||||
const SPECIAL_DATA_OPTIONS = [
|
||||
{ id: 'health', label: 'Gesundheitsdaten' },
|
||||
{ id: 'biometric', label: 'Biometrische Daten' },
|
||||
{ id: 'ethnicity', label: 'Religiöse / ethnische Herkunft' },
|
||||
{ id: 'sexual', label: 'Sexuelle Orientierung' },
|
||||
{ id: 'criminal', label: 'Strafrechtliche Daten' },
|
||||
{ id: 'minors', label: 'Minderjährige (<16)' },
|
||||
{ id: 'none', label: 'Keine besonderen Daten' },
|
||||
]
|
||||
|
||||
const STORAGE_KEY = 'compliance-scan-context'
|
||||
|
||||
function emptyContext(): ScanContext {
|
||||
return {
|
||||
industry: '',
|
||||
business_model: '',
|
||||
direct_sales: '',
|
||||
legal_form: '',
|
||||
group_structure: '',
|
||||
employee_count: '',
|
||||
special_data: [],
|
||||
third_country_transfer: '',
|
||||
}
|
||||
}
|
||||
|
||||
export function isContextComplete(ctx: ScanContext): boolean {
|
||||
return Boolean(
|
||||
ctx.industry &&
|
||||
ctx.business_model &&
|
||||
ctx.direct_sales &&
|
||||
ctx.legal_form &&
|
||||
ctx.group_structure &&
|
||||
ctx.employee_count &&
|
||||
ctx.special_data.length > 0 &&
|
||||
ctx.third_country_transfer
|
||||
)
|
||||
}
|
||||
|
||||
export function PreScanWizard({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value: ScanContext
|
||||
onChange: (ctx: ScanContext) => void
|
||||
}) {
|
||||
const update = <K extends keyof ScanContext>(key: K, val: ScanContext[K]) => {
|
||||
onChange({ ...value, [key]: val })
|
||||
}
|
||||
|
||||
const toggleSpecialData = (id: string) => {
|
||||
const next = value.special_data.includes(id)
|
||||
? value.special_data.filter(x => x !== id)
|
||||
: [...value.special_data.filter(x => x !== 'none' || id === 'none'), id]
|
||||
onChange({ ...value, special_data: id === 'none' ? ['none'] : next.filter(x => x !== 'none') })
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
background: '#f0f9ff',
|
||||
border: '1px solid #bfdbfe',
|
||||
borderRadius: 8,
|
||||
padding: '14px 16px',
|
||||
marginBottom: 14,
|
||||
}}>
|
||||
<div style={{ fontSize: 11, color: '#1e40af', textTransform: 'uppercase',
|
||||
letterSpacing: 1.2, marginBottom: 4, fontWeight: 600 }}>
|
||||
Pflichtangaben zur Klassifizierung des Audits
|
||||
</div>
|
||||
<h3 style={{ margin: '0 0 6px', fontSize: 14, color: '#1e293b' }}>
|
||||
Vor dem Scan: 8 Angaben zum Unternehmen
|
||||
</h3>
|
||||
<p style={{ margin: '0 0 12px', fontSize: 11, color: '#475569', lineHeight: 1.5 }}>
|
||||
Diese Angaben filtern irrelevante Compliance-Themen heraus (z.B. eHealth-
|
||||
Vorschriften bei einem Autobauer) und liefern eine realistische
|
||||
Einschätzung statt pauschaler Verstoss-Listen.
|
||||
</p>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 10 }}>
|
||||
<Field label="1. Branche*">
|
||||
<select value={value.industry} onChange={e => update('industry', e.target.value)} style={inputStyle}>
|
||||
{INDUSTRIES.map(o => <option key={o.id} value={o.id}>{o.label}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
|
||||
<Field label="2. Geschäftsmodell*">
|
||||
<select value={value.business_model} onChange={e => update('business_model', e.target.value)} style={inputStyle}>
|
||||
<option value="">— bitte wählen —</option>
|
||||
<option value="b2b">B2B</option>
|
||||
<option value="b2c">B2C</option>
|
||||
<option value="both">Beides (B2B + B2C)</option>
|
||||
</select>
|
||||
</Field>
|
||||
|
||||
<Field label="3. Direkt-Vertrieb (Webshop/Buchung)*">
|
||||
<select value={value.direct_sales} onChange={e => update('direct_sales', e.target.value)} style={inputStyle}>
|
||||
<option value="">— bitte wählen —</option>
|
||||
<option value="yes">Ja</option>
|
||||
<option value="no">Nein</option>
|
||||
<option value="lead_funnel">Nur Lead-Funnel (Probefahrten, Anfragen)</option>
|
||||
</select>
|
||||
</Field>
|
||||
|
||||
<Field label="4. Rechtsform*">
|
||||
<select value={value.legal_form} onChange={e => update('legal_form', e.target.value)} style={inputStyle}>
|
||||
{LEGAL_FORMS.map(o => <option key={o.id} value={o.id}>{o.label}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
|
||||
<Field label="5. Konzern-Struktur*">
|
||||
<select value={value.group_structure} onChange={e => update('group_structure', e.target.value)} style={inputStyle}>
|
||||
{GROUP_STRUCTURES.map(o => <option key={o.id} value={o.id}>{o.label}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
|
||||
<Field label="6. Mitarbeiterzahl*">
|
||||
<select value={value.employee_count} onChange={e => update('employee_count', e.target.value)} style={inputStyle}>
|
||||
{EMPLOYEE_COUNTS.map(o => <option key={o.id} value={o.id}>{o.label}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
|
||||
<Field label="7. Besondere Datenkategorien*" colSpan={2}>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
||||
{SPECIAL_DATA_OPTIONS.map(o => (
|
||||
<label key={o.id} style={{ fontSize: 12, display: 'inline-flex',
|
||||
alignItems: 'center', gap: 4,
|
||||
padding: '4px 8px', background: '#fff',
|
||||
border: '1px solid #cbd5e1',
|
||||
borderRadius: 4 }}>
|
||||
<input type="checkbox"
|
||||
checked={value.special_data.includes(o.id)}
|
||||
onChange={() => toggleSpecialData(o.id)} />
|
||||
{o.label}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</Field>
|
||||
|
||||
<Field label="8. Bekannter Drittland-Transfer*" colSpan={2}>
|
||||
<select value={value.third_country_transfer} onChange={e => update('third_country_transfer', e.target.value)} style={inputStyle}>
|
||||
<option value="">— bitte wählen —</option>
|
||||
<option value="yes">Ja (USA, CN, IN, UK, ...)</option>
|
||||
<option value="no">Nein (nur EU/EWR)</option>
|
||||
<option value="unknown">Weiß nicht (bitte automatisch prüfen)</option>
|
||||
</select>
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
{!isContextComplete(value) && (
|
||||
<div style={{ marginTop: 10, fontSize: 11, color: '#92400e',
|
||||
background: '#fef3c7', padding: '6px 10px',
|
||||
borderRadius: 4, border: '1px solid #fde68a' }}>
|
||||
Bitte alle 8 Pflichtfelder ausfüllen — der Scan-Button wird erst aktiv,
|
||||
wenn die Klassifizierung komplett ist.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const inputStyle: React.CSSProperties = {
|
||||
width: '100%',
|
||||
padding: '6px 8px',
|
||||
fontSize: 12,
|
||||
border: '1px solid #cbd5e1',
|
||||
borderRadius: 4,
|
||||
background: '#fff',
|
||||
}
|
||||
|
||||
function Field({ label, children, colSpan }: { label: string; children: React.ReactNode; colSpan?: number }) {
|
||||
return (
|
||||
<div style={{ gridColumn: colSpan ? `span ${colSpan}` : undefined }}>
|
||||
<label style={{ display: 'block', fontSize: 11, color: '#475569',
|
||||
marginBottom: 4, fontWeight: 600 }}>
|
||||
{label}
|
||||
</label>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function useScanContext(): [ScanContext, (ctx: ScanContext) => void] {
|
||||
const [ctx, setCtx] = useState<ScanContext>(() => {
|
||||
if (typeof window === 'undefined') return emptyContext()
|
||||
try {
|
||||
const s = localStorage.getItem(STORAGE_KEY)
|
||||
return s ? { ...emptyContext(), ...JSON.parse(s) } : emptyContext()
|
||||
} catch {
|
||||
return emptyContext()
|
||||
}
|
||||
})
|
||||
useEffect(() => {
|
||||
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(ctx)) } catch {}
|
||||
}, [ctx])
|
||||
return [ctx, setCtx]
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* ResultsTabsView — strukturierte Tab-Ansicht der Audit-Ergebnisse.
|
||||
*
|
||||
* Statt einer langen Scroll-Seite gibt es:
|
||||
* 1. Übersicht (Score + GF-Kurzfassung)
|
||||
* 2. Cookies (3-Quellen-Compliance-Vergleich + Vendor-/Cookie-Listen)
|
||||
* 3. Datenschutzerklärung
|
||||
* 4. Impressum
|
||||
* 5. AGB / Widerruf
|
||||
* 6. Banner (Cookie-Banner-Checks)
|
||||
* 7. Vollständige Mail (HTML-Preview)
|
||||
*
|
||||
* Tab-Headers sticky oben, Content scrollbar unten.
|
||||
*/
|
||||
|
||||
import React, { useState, useMemo } from 'react'
|
||||
import { ChecklistView } from './ChecklistView'
|
||||
|
||||
interface ResultsTabsViewProps {
|
||||
results: any
|
||||
}
|
||||
|
||||
type TabId = 'overview' | 'cookies' | 'dse' | 'impressum' | 'agb' | 'banner' | 'mail'
|
||||
|
||||
const TABS: { id: TabId; label: string; icon: string }[] = [
|
||||
{ id: 'overview', label: 'Übersicht', icon: '◉' },
|
||||
{ id: 'cookies', label: 'Cookies & VVT', icon: '🍪' },
|
||||
{ id: 'dse', label: 'Datenschutzerkl.', icon: '📄' },
|
||||
{ id: 'impressum', label: 'Impressum', icon: '🏢' },
|
||||
{ id: 'agb', label: 'AGB / Widerruf', icon: '⚖️' },
|
||||
{ id: 'banner', label: 'Cookie-Banner', icon: '🎛' },
|
||||
{ id: 'mail', label: 'Mail-Vorschau', icon: '✉️' },
|
||||
]
|
||||
|
||||
export function ResultsTabsView({ results }: ResultsTabsViewProps) {
|
||||
const [active, setActive] = useState<TabId>('overview')
|
||||
|
||||
const r = results || {}
|
||||
const docs: any[] = r.results || []
|
||||
const banner = r.banner_result || r.cookie_banner_result || {}
|
||||
const cmpVendors: any[] = r.cmp_vendors || []
|
||||
const cookieAudit = r.cookie_audit || {}
|
||||
|
||||
const docsByType = useMemo(() => {
|
||||
const m: Record<string, any> = {}
|
||||
for (const d of docs) {
|
||||
const t = (d.doc_type || '').toLowerCase()
|
||||
if (!m[t]) m[t] = d
|
||||
}
|
||||
return m
|
||||
}, [docs])
|
||||
|
||||
return (
|
||||
<div className="border border-gray-200 rounded-lg overflow-hidden bg-white">
|
||||
{/* Sticky Tab-Header */}
|
||||
<div className="flex border-b border-gray-200 bg-gray-50 overflow-x-auto sticky top-0 z-10">
|
||||
{TABS.map(t => (
|
||||
<button
|
||||
key={t.id}
|
||||
onClick={() => setActive(t.id)}
|
||||
className={`px-4 py-3 text-sm font-medium whitespace-nowrap border-b-2 transition-colors ${
|
||||
active === t.id
|
||||
? 'border-purple-600 text-purple-700 bg-white'
|
||||
: 'border-transparent text-gray-600 hover:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
<span className="mr-1.5">{t.icon}</span>
|
||||
{t.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Tab-Content */}
|
||||
<div className="p-4 min-h-[400px]">
|
||||
{active === 'overview' && <OverviewTab results={r} />}
|
||||
{active === 'cookies' && (
|
||||
<CookiesTab
|
||||
audit={cookieAudit}
|
||||
vendors={cmpVendors}
|
||||
banner={banner}
|
||||
/>
|
||||
)}
|
||||
{active === 'dse' && <DocTab doc={docsByType['dse']} label="Datenschutzerklärung" />}
|
||||
{active === 'impressum' && <DocTab doc={docsByType['impressum']} label="Impressum" />}
|
||||
{active === 'agb' && <AgbWiderrufTab docs={docsByType} />}
|
||||
{active === 'banner' && <BannerTab banner={banner} />}
|
||||
{active === 'mail' && <MailPreviewTab results={r} />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ── Übersicht ──────────────────────────────────────────────────────────
|
||||
function OverviewTab({ results }: { results: any }) {
|
||||
const totalDocs = results.total_documents || (results.results?.length ?? 0)
|
||||
const totalFindings = results.total_findings ?? 0
|
||||
const banner = results.banner_result || results.cookie_banner_result || {}
|
||||
const score = banner.compliance_score ?? banner.completeness_pct ?? null
|
||||
const emailStatus = results.email_status
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
<Kpi label="Geprüfte Dokumente" value={totalDocs} />
|
||||
<Kpi label="Findings gesamt" value={totalFindings} tone={totalFindings > 5 ? 'warn' : 'ok'} />
|
||||
<Kpi label="Vendors erkannt" value={results.cmp_vendors?.length || 0} />
|
||||
<Kpi label="Score" value={score !== null ? `${score}%` : '—'}
|
||||
tone={score === null ? 'neutral' : score >= 80 ? 'ok' : score >= 60 ? 'warn' : 'bad'} />
|
||||
</div>
|
||||
|
||||
{emailStatus && (
|
||||
<div className={`text-sm px-3 py-2 rounded ${
|
||||
emailStatus === 'sent' ? 'bg-green-50 text-green-800' : 'bg-gray-100 text-gray-700'
|
||||
}`}>
|
||||
E-Mail: {emailStatus === 'sent' ? '✓ Gesendet an Empfänger' : emailStatus}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="bg-blue-50 border border-blue-200 rounded p-3 text-xs text-blue-900">
|
||||
<strong>Wo welcher Inhalt steckt:</strong> in den Tabs oben findest du die
|
||||
Detail-Auswertung pro Doc-Typ. Im Cookie-Tab steht der 3-Quellen-Compliance-
|
||||
Vergleich (deklariert vs Browser vs Library) — das ist der wichtigste
|
||||
rechtliche Knackpunkt. Banner-Tab zeigt die echten Browser-Phasen-Checks.
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Kpi({ label, value, tone = 'neutral' }: { label: string; value: any; tone?: string }) {
|
||||
const colors: Record<string, string> = {
|
||||
ok: 'text-green-700 bg-green-50 border-green-200',
|
||||
warn: 'text-amber-700 bg-amber-50 border-amber-200',
|
||||
bad: 'text-red-700 bg-red-50 border-red-200',
|
||||
neutral: 'text-gray-700 bg-gray-50 border-gray-200',
|
||||
}
|
||||
return (
|
||||
<div className={`border rounded p-3 ${colors[tone]}`}>
|
||||
<div className="text-[10px] uppercase tracking-wider opacity-70">{label}</div>
|
||||
<div className="text-2xl font-bold mt-1">{value}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ── Cookies & VVT ──────────────────────────────────────────────────────
|
||||
function CookiesTab({ audit, vendors, banner }: { audit: any; vendors: any[]; banner: any }) {
|
||||
const declared = audit?.declared_count ?? 0
|
||||
const browser = audit?.browser_count ?? 0
|
||||
const both = (audit?.compliant ?? []).length
|
||||
const undecl = (audit?.undeclared_in_browser ?? []).length
|
||||
const decOnly = (audit?.declared_not_loaded ?? []).length
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Top-Bar mit Counts */}
|
||||
<div className="grid grid-cols-3 md:grid-cols-5 gap-2">
|
||||
<Kpi label="Deklariert" value={declared} />
|
||||
<Kpi label="Im Browser" value={browser} />
|
||||
<Kpi label="Compliant" value={both} tone="ok" />
|
||||
<Kpi label="Undokumentiert" value={undecl} tone={undecl > 0 ? 'bad' : 'ok'} />
|
||||
<Kpi label="Nicht geladen" value={decOnly} tone={decOnly > 0 ? 'warn' : 'neutral'} />
|
||||
</div>
|
||||
|
||||
{/* 3-Spalten-Vergleichstabelle */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
<CookieColumn
|
||||
title={`❌ Undokumentiert (${undecl})`}
|
||||
tone="bad"
|
||||
subtitle="Geladen ABER nicht in der Richtlinie — Art. 13(1)(c) DSGVO Verstoß"
|
||||
cookies={audit?.undeclared_in_browser ?? []}
|
||||
/>
|
||||
<CookieColumn
|
||||
title={`✓ Compliant (${both})`}
|
||||
tone="ok"
|
||||
subtitle="Beide Quellen stimmen überein"
|
||||
cookies={audit?.compliant ?? []}
|
||||
/>
|
||||
<CookieColumn
|
||||
title={`⚠️ Nicht geladen (${decOnly})`}
|
||||
tone="warn"
|
||||
subtitle="In Richtlinie deklariert, aber bei diesem Lauf nicht im Browser"
|
||||
cookies={audit?.declared_not_loaded ?? []}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Vendor-Liste (deduped) */}
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold mb-2 text-gray-800">
|
||||
Vendor-Liste ({vendors.length} unique nach Deduplizierung)
|
||||
</h3>
|
||||
<div className="overflow-x-auto border border-gray-200 rounded">
|
||||
<table className="w-full text-xs">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="text-left px-3 py-2">Vendor</th>
|
||||
<th className="text-left px-3 py-2">Kategorie</th>
|
||||
<th className="text-left px-3 py-2">Quelle</th>
|
||||
<th className="text-right px-3 py-2">Cookies</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{vendors.map((v, i) => (
|
||||
<tr key={i} className="border-t border-gray-100 hover:bg-gray-50">
|
||||
<td className="px-3 py-2 font-medium">{v.name}</td>
|
||||
<td className="px-3 py-2 text-gray-600">{v.category || '—'}</td>
|
||||
<td className="px-3 py-2 text-gray-500 font-mono text-[10px]">
|
||||
{v.source || '—'}
|
||||
</td>
|
||||
<td className="px-3 py-2 text-right">{(v.cookies || []).length}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CookieColumn({ title, tone, subtitle, cookies }: {
|
||||
title: string; tone: string; subtitle: string; cookies: string[]
|
||||
}) {
|
||||
const colors: Record<string, string> = {
|
||||
bad: 'bg-red-50 border-red-200 text-red-900',
|
||||
ok: 'bg-green-50 border-green-200 text-green-900',
|
||||
warn: 'bg-amber-50 border-amber-200 text-amber-900',
|
||||
}
|
||||
return (
|
||||
<div className={`border rounded p-3 ${colors[tone]}`}>
|
||||
<div className="text-xs font-semibold mb-1">{title}</div>
|
||||
<div className="text-[10px] opacity-80 mb-2">{subtitle}</div>
|
||||
<div className="font-mono text-[10px] max-h-56 overflow-auto">
|
||||
{cookies.length === 0 && <span className="opacity-60">— keine —</span>}
|
||||
{cookies.map((c, i) => (
|
||||
<div key={i} className="py-0.5">{c}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ── Generic Doc-Tab ────────────────────────────────────────────────────
|
||||
function DocTab({ doc, label }: { doc: any; label: string }) {
|
||||
if (!doc) return <Empty label={label} />
|
||||
const checks = doc.checks || []
|
||||
const failed = checks.filter((c: any) => !c.passed && !c.skipped)
|
||||
const passed = checks.filter((c: any) => c.passed)
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-semibold">{label}</h3>
|
||||
<div className="text-xs text-gray-600">
|
||||
{doc.word_count?.toLocaleString('de-DE') || 0} Wörter ·{' '}
|
||||
<span className="text-red-600">{failed.length} Findings</span> ·{' '}
|
||||
<span className="text-green-600">{passed.length} OK</span>
|
||||
</div>
|
||||
</div>
|
||||
{doc.url && (
|
||||
<a href={doc.url} target="_blank" rel="noreferrer"
|
||||
className="text-xs text-blue-600 hover:underline break-all">
|
||||
{doc.url}
|
||||
</a>
|
||||
)}
|
||||
<ChecklistView results={[doc]} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function AgbWiderrufTab({ docs }: { docs: Record<string, any> }) {
|
||||
const agb = docs['agb'] || docs['nutzungsbedingungen']
|
||||
const wid = docs['widerruf']
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold mb-2">AGB / Nutzungsbedingungen</h3>
|
||||
{agb ? <ChecklistView results={[agb]} /> : <Empty label="AGB" inline />}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold mb-2">Widerrufsbelehrung</h3>
|
||||
{wid ? <ChecklistView results={[wid]} /> : <Empty label="Widerruf" inline />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function BannerTab({ banner }: { banner: any }) {
|
||||
if (!banner || Object.keys(banner).length === 0) return <Empty label="Cookie-Banner" />
|
||||
const phases = banner.phases || {}
|
||||
const violations = banner.banner_checks?.violations || []
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="text-xs text-gray-700">
|
||||
Banner erkannt: <strong>{banner.banner_detected ? 'Ja' : 'Nein'}</strong> ·{' '}
|
||||
Provider: <strong>{banner.banner_provider || '—'}</strong> ·{' '}
|
||||
Verstöße: <strong>{violations.length}</strong>
|
||||
</div>
|
||||
{violations.length > 0 && (
|
||||
<div className="border border-red-200 bg-red-50 rounded p-3">
|
||||
<div className="text-xs font-semibold text-red-800 mb-2">Verstöße</div>
|
||||
<ul className="text-xs text-red-900 space-y-1">
|
||||
{violations.map((v: any, i: number) => (
|
||||
<li key={i}>• {v.label || v.message || JSON.stringify(v)}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{Object.entries(phases).map(([name, ph]: [string, any]) => (
|
||||
<div key={name} className="border border-gray-200 rounded p-2">
|
||||
<div className="text-[10px] uppercase text-gray-500">{name}</div>
|
||||
<div className="text-xs mt-1">
|
||||
Cookies: <strong>{ph.cookies?.length || 0}</strong>
|
||||
</div>
|
||||
<div className="text-xs">
|
||||
Vendors: <strong>{ph.vendors?.length || 0}</strong>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function MailPreviewTab({ results }: { results: any }) {
|
||||
return (
|
||||
<div className="text-xs text-gray-600 space-y-2">
|
||||
<p>
|
||||
Die vollständige Mail wurde {results.email_status === 'sent' ? 'gesendet' : 'erstellt'}.
|
||||
Snapshot-ID:{' '}
|
||||
<code className="bg-gray-100 px-1.5 py-0.5 rounded">{results.check_id || '—'}</code>
|
||||
</p>
|
||||
{results.check_id && (
|
||||
<a
|
||||
href={`/api/compliance/agent/snapshots/${results.check_id}/pdf`}
|
||||
target="_blank" rel="noreferrer"
|
||||
className="inline-block text-purple-600 hover:underline"
|
||||
>
|
||||
→ PDF der Mail herunterladen
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Empty({ label, inline }: { label: string; inline?: boolean }) {
|
||||
return (
|
||||
<div className={`text-xs text-gray-500 ${inline ? '' : 'py-8 text-center'}`}>
|
||||
Keine Daten für „{label}" in diesem Lauf.
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* P47 — localStorage-Quota-Management.
|
||||
*
|
||||
* Wenn alte Compliance-Check-Ergebnisse den Browser-Storage fuellen,
|
||||
* versucht das setItem mit QuotaExceededError zu fangen, prunet
|
||||
* alte doc-check-result-*-Eintraege (oldest first) und retried.
|
||||
*
|
||||
* Wird von DocCheckTab/BannerCheckTab/etc beim Persistieren der
|
||||
* Result-Bloebs benutzt.
|
||||
*/
|
||||
|
||||
const RESULT_KEY_PREFIX = 'doc-check-result-'
|
||||
const MAX_KEEP = 10 // Maximal 10 alte Result-Bloebs behalten.
|
||||
|
||||
export function safeSetItem(key: string, value: string): boolean {
|
||||
try {
|
||||
localStorage.setItem(key, value)
|
||||
return true
|
||||
} catch (err: any) {
|
||||
if (err?.name !== 'QuotaExceededError'
|
||||
&& err?.code !== 22 && err?.code !== 1014) {
|
||||
console.warn('localStorage setItem failed:', err)
|
||||
return false
|
||||
}
|
||||
pruneOldResults()
|
||||
try {
|
||||
localStorage.setItem(key, value)
|
||||
return true
|
||||
} catch {
|
||||
// Pruning hat nicht gereicht — aggressiver pruefen
|
||||
pruneOldResults(0)
|
||||
try {
|
||||
localStorage.setItem(key, value)
|
||||
return true
|
||||
} catch {
|
||||
console.warn('localStorage immer noch voll, wert wird verworfen')
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pruneOldResults(keep: number = MAX_KEEP): void {
|
||||
try {
|
||||
const keys: { key: string; ts: number }[] = []
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const k = localStorage.key(i)
|
||||
if (!k || !k.startsWith(RESULT_KEY_PREFIX)) continue
|
||||
const ts = Number(k.slice(RESULT_KEY_PREFIX.length)) || 0
|
||||
keys.push({ key: k, ts })
|
||||
}
|
||||
keys.sort((a, b) => a.ts - b.ts) // oldest first
|
||||
const toRemove = keys.slice(0, Math.max(0, keys.length - keep))
|
||||
for (const k of toRemove) {
|
||||
try { localStorage.removeItem(k.key) } catch {}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export function getStorageUsageMB(): number {
|
||||
let bytes = 0
|
||||
try {
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const k = localStorage.key(i)
|
||||
if (!k) continue
|
||||
const v = localStorage.getItem(k) || ''
|
||||
bytes += k.length + v.length
|
||||
}
|
||||
} catch {}
|
||||
return bytes / (1024 * 1024)
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
'use client'
|
||||
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
type Phase = {
|
||||
cookies?: string[]
|
||||
scripts?: string[]
|
||||
tracking_services?: (string | { name?: string })[]
|
||||
new_tracking?: unknown[]
|
||||
violations?: Array<{ severity?: string; text?: string }>
|
||||
undocumented?: unknown[]
|
||||
}
|
||||
|
||||
type CategoryTest = {
|
||||
category: string
|
||||
category_label: string
|
||||
tracking_services?: (string | { name?: string })[]
|
||||
cookies_set?: string[]
|
||||
provider_details_visible?: boolean
|
||||
violations?: Array<{ severity?: string; text?: string; legal_ref?: string }>
|
||||
}
|
||||
|
||||
type BannerViolation = {
|
||||
severity?: string
|
||||
text?: string
|
||||
legal_ref?: string
|
||||
}
|
||||
|
||||
type StructuredCheck = {
|
||||
id: string
|
||||
label: string
|
||||
passed: boolean
|
||||
skipped?: boolean
|
||||
severity: string
|
||||
level?: number
|
||||
hint?: string
|
||||
}
|
||||
|
||||
type BannerResp = {
|
||||
found: boolean
|
||||
check_id: string
|
||||
banner?: {
|
||||
banner_provider?: string
|
||||
banner_detected?: boolean
|
||||
completeness_pct?: number
|
||||
correctness_pct?: number
|
||||
phases?: Record<string, Phase>
|
||||
banner_checks?: { violations?: BannerViolation[] }
|
||||
category_tests?: CategoryTest[]
|
||||
structured_checks?: StructuredCheck[]
|
||||
summary?: Record<string, number>
|
||||
}
|
||||
}
|
||||
|
||||
const PHASE_LABEL: Record<string, string> = {
|
||||
before_consent: 'Vor Consent',
|
||||
after_reject: 'Nach Ablehnung',
|
||||
after_accept: 'Nach Annahme',
|
||||
}
|
||||
|
||||
const SEV_BADGE: Record<string, string> = {
|
||||
CRITICAL: 'bg-red-600 text-white',
|
||||
HIGH: 'bg-red-100 text-red-800',
|
||||
MEDIUM: 'bg-amber-100 text-amber-800',
|
||||
LOW: 'bg-blue-100 text-blue-800',
|
||||
INFO: 'bg-gray-100 text-gray-600',
|
||||
}
|
||||
|
||||
function pctColor(pct?: number): string {
|
||||
if (pct === undefined || pct === null) return 'text-gray-400'
|
||||
return pct >= 80 ? 'text-green-700' : pct >= 50 ? 'text-amber-700' : 'text-red-700'
|
||||
}
|
||||
|
||||
export default function BannerTab({ checkId }: { checkId: string }) {
|
||||
const [data, setData] = useState<BannerResp | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [checkFilter, setCheckFilter] = useState<'all' | 'fail' | 'critical'>('fail')
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
setLoading(true)
|
||||
fetch(`/api/sdk/v1/agent/banner/${checkId}`)
|
||||
.then(r => r.json())
|
||||
.then(d => { if (!cancelled) setData(d) })
|
||||
.catch(e => { if (!cancelled) setError(String(e)) })
|
||||
.finally(() => { if (!cancelled) setLoading(false) })
|
||||
return () => { cancelled = true }
|
||||
}, [checkId])
|
||||
|
||||
if (loading) return <div className="p-6 text-sm text-gray-500">Lade Banner-Daten…</div>
|
||||
if (error) return <div className="p-6 text-sm text-red-600">Fehler: {error}</div>
|
||||
if (!data?.found || !data.banner) {
|
||||
return <div className="p-6 text-sm text-gray-500">Keine Banner-Daten zu diesem Check.</div>
|
||||
}
|
||||
|
||||
const b = data.banner
|
||||
const phases = b.phases || {}
|
||||
const cats = b.category_tests || []
|
||||
const violations = b.banner_checks?.violations || []
|
||||
const checks = b.structured_checks || []
|
||||
const summary = b.summary || {}
|
||||
|
||||
const filteredChecks = checks.filter(c => {
|
||||
if (checkFilter === 'all') return true
|
||||
if (checkFilter === 'fail') return !c.passed && !c.skipped
|
||||
return !c.passed && !c.skipped && ['CRITICAL', 'HIGH'].includes(c.severity)
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Quality Cards */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 text-xs">
|
||||
<div className="border rounded p-3">
|
||||
<div className="text-[10px] uppercase text-gray-500">Vollstaendigkeit</div>
|
||||
<div className={`text-2xl font-semibold ${pctColor(b.completeness_pct)}`}>
|
||||
{b.completeness_pct ?? '–'}{b.completeness_pct !== undefined && '%'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="border rounded p-3">
|
||||
<div className="text-[10px] uppercase text-gray-500">Korrektheit</div>
|
||||
<div className={`text-2xl font-semibold ${pctColor(b.correctness_pct)}`}>
|
||||
{b.correctness_pct ?? '–'}{b.correctness_pct !== undefined && '%'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="border rounded p-3">
|
||||
<div className="text-[10px] uppercase text-gray-500">Verstoesse</div>
|
||||
<div className="text-2xl font-semibold text-red-700">
|
||||
{summary.total_violations ?? violations.length}
|
||||
</div>
|
||||
<div className="text-[10px] text-gray-500 mt-1">
|
||||
crit:{summary.critical ?? 0} · high:{summary.high ?? 0}
|
||||
</div>
|
||||
</div>
|
||||
<div className="border rounded p-3">
|
||||
<div className="text-[10px] uppercase text-gray-500">CMP</div>
|
||||
<div className="text-sm font-medium text-gray-800 truncate">
|
||||
{b.banner_provider || 'unbekannt'}
|
||||
</div>
|
||||
<div className="text-[10px] text-gray-500 mt-1">
|
||||
{b.banner_detected ? 'Banner erkannt' : 'kein Banner'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Phases */}
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<div className="px-4 py-2 bg-gray-50 border-b text-sm font-medium text-gray-700">
|
||||
Cookie-Setzungen pro Phase (echter Browser-Test)
|
||||
</div>
|
||||
<table className="w-full text-xs">
|
||||
<thead className="bg-gray-50 text-gray-600">
|
||||
<tr>
|
||||
<th className="px-3 py-2 text-left">Phase</th>
|
||||
<th className="px-3 py-2 text-center">Cookies</th>
|
||||
<th className="px-3 py-2 text-center">Tracker</th>
|
||||
<th className="px-3 py-2 text-left">Auffaelligkeiten</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{(['before_consent', 'after_reject', 'after_accept'] as const).map(key => {
|
||||
const p = phases[key] || {}
|
||||
const nc = (p.cookies || []).length
|
||||
const nt = (p.tracking_services || []).length
|
||||
const issues: string[] = []
|
||||
if (p.violations?.length) issues.push(`${p.violations.length} Verstoss`)
|
||||
if (p.new_tracking?.length) issues.push(`${p.new_tracking.length} neue Tracker`)
|
||||
if (p.undocumented?.length) issues.push(`${p.undocumented.length} undokumentiert`)
|
||||
const color = key === 'before_consent'
|
||||
? (nc === 0 ? 'text-green-600' : 'text-red-600')
|
||||
: key === 'after_reject'
|
||||
? (nc <= 1 ? 'text-green-600' : 'text-amber-600')
|
||||
: 'text-gray-700'
|
||||
return (
|
||||
<tr key={key} className="border-t">
|
||||
<td className="px-3 py-2 font-medium">{PHASE_LABEL[key]}</td>
|
||||
<td className={`px-3 py-2 text-center font-semibold ${color}`}>{nc}</td>
|
||||
<td className="px-3 py-2 text-center">{nt}</td>
|
||||
<td className="px-3 py-2 text-gray-500">{issues.join(', ') || '—'}</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Per-Category */}
|
||||
{cats.length > 0 && (
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<div className="px-4 py-2 bg-gray-50 border-b text-sm font-medium text-gray-700">
|
||||
Provider-Listing pro Kategorie (P19 Click-Through-Test)
|
||||
</div>
|
||||
<table className="w-full text-xs">
|
||||
<thead className="bg-gray-50 text-gray-600">
|
||||
<tr>
|
||||
<th className="px-3 py-2 text-left">Kategorie</th>
|
||||
<th className="px-3 py-2 text-center">Anbieter sichtbar</th>
|
||||
<th className="px-3 py-2 text-center">Tracker erkannt</th>
|
||||
<th className="px-3 py-2 text-left">Violations</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{cats.map(c => {
|
||||
const pdv = c.provider_details_visible
|
||||
const pdv_label = pdv === true ? 'Ja' : pdv === false ? 'Nein' : '–'
|
||||
const pdv_color = pdv === false ? 'text-red-700' : pdv === true ? 'text-green-700' : 'text-gray-400'
|
||||
return (
|
||||
<tr key={c.category} className="border-t">
|
||||
<td className="px-3 py-2">{c.category_label}</td>
|
||||
<td className={`px-3 py-2 text-center font-semibold ${pdv_color}`}>{pdv_label}</td>
|
||||
<td className="px-3 py-2 text-center">{(c.tracking_services || []).length}</td>
|
||||
<td className="px-3 py-2 text-red-700 text-[10px]">
|
||||
{(c.violations || []).map(v => v.text?.slice(0, 80)).join('; ') || '—'}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Banner-Checks Violations */}
|
||||
{violations.length > 0 && (
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<div className="px-4 py-2 bg-gray-50 border-b text-sm font-medium text-gray-700">
|
||||
Banner-Verstoesse ({violations.length})
|
||||
</div>
|
||||
<ul className="text-xs divide-y">
|
||||
{violations.map((v, i) => {
|
||||
const sev = (v.severity || 'MEDIUM').toUpperCase()
|
||||
return (
|
||||
<li key={i} className="px-3 py-2">
|
||||
<div className="flex items-start gap-2">
|
||||
<span className={`px-1.5 py-0.5 rounded text-[10px] font-medium ${SEV_BADGE[sev] || 'bg-gray-100'}`}>{sev}</span>
|
||||
<div>
|
||||
<div className="text-gray-900">{v.text}</div>
|
||||
{v.legal_ref && <div className="text-[10px] text-gray-400 italic mt-1">Quelle: {v.legal_ref}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 46 structured_checks Drilldown */}
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<div className="px-4 py-2 bg-gray-50 border-b text-sm font-medium text-gray-700 flex items-center gap-3">
|
||||
<span>Banner-Checks ({checks.length})</span>
|
||||
<div className="ml-auto flex gap-1">
|
||||
{(['all', 'fail', 'critical'] as const).map(f => (
|
||||
<button key={f}
|
||||
onClick={() => setCheckFilter(f)}
|
||||
className={`px-2 py-1 rounded text-[10px] border ${
|
||||
checkFilter === f ? 'bg-blue-600 text-white border-blue-600'
|
||||
: 'bg-white text-gray-600 border-gray-200'
|
||||
}`}>
|
||||
{f === 'all' ? 'Alle' : f === 'fail' ? 'Nur Fail' : 'Nur CRIT/HIGH'}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<table className="w-full text-xs">
|
||||
<thead className="bg-gray-50 text-gray-600">
|
||||
<tr>
|
||||
<th className="px-3 py-2 text-left">Status</th>
|
||||
<th className="px-3 py-2 text-left">Sev</th>
|
||||
<th className="px-3 py-2 text-left">Check</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredChecks.map(c => (
|
||||
<tr key={c.id} className="border-t">
|
||||
<td className="px-3 py-2">
|
||||
{c.passed ? <span className="text-green-600">✓</span>
|
||||
: c.skipped ? <span className="text-gray-400">—</span>
|
||||
: <span className="text-red-600">✗</span>}
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<span className={`px-1.5 py-0.5 rounded text-[10px] font-medium ${SEV_BADGE[c.severity] || 'bg-gray-100'}`}>
|
||||
{c.severity}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<div className="text-gray-900">{c.label}</div>
|
||||
{c.hint && !c.passed && (
|
||||
<div className="text-[10px] text-gray-500 mt-1">{c.hint.slice(0, 200)}</div>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{filteredChecks.length === 0 && (
|
||||
<tr><td colSpan={3} className="px-3 py-4 text-center text-gray-400">Keine Checks fuer den Filter.</td></tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import { use as useUnwrap } from 'react'
|
||||
import FindingsTab from './FindingsTab'
|
||||
import BannerTab from './BannerTab'
|
||||
|
||||
type MCRow = {
|
||||
id: number
|
||||
@@ -92,7 +93,7 @@ export default function AuditPage(
|
||||
const [filterReg, setFilterReg] = useState<string>('')
|
||||
const [filterDoc, setFilterDoc] = useState<string>('')
|
||||
const [expanded, setExpanded] = useState<number | null>(null)
|
||||
const [tab, setTab] = useState<'mc' | 'all'>('all')
|
||||
const [tab, setTab] = useState<'mc' | 'all' | 'banner'>('all')
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
@@ -155,6 +156,7 @@ export default function AuditPage(
|
||||
<div className="flex gap-2 border-b border-gray-200">
|
||||
{([
|
||||
{ key: 'all', label: 'Voll-Audit (alle Findings)' },
|
||||
{ key: 'banner', label: 'Cookie-Banner-Analyse' },
|
||||
{ key: 'mc', label: 'Nur MC-Scorecard' },
|
||||
] as const).map(t => (
|
||||
<button key={t.key}
|
||||
@@ -168,6 +170,7 @@ export default function AuditPage(
|
||||
</div>
|
||||
|
||||
{tab === 'all' && <FindingsTab checkId={checkId} />}
|
||||
{tab === 'banner' && <BannerTab checkId={checkId} />}
|
||||
|
||||
{tab === 'mc' && <>
|
||||
{/* Scorecard */}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
|
||||
interface Props {
|
||||
/** Risk classification of the AI system. Tile is only rendered for high_risk / unacceptable. */
|
||||
riskLevel: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a tile pointing to the BSI QUAIDAL-based data-quality control tab.
|
||||
* AI Act Article 10 obligations (training-data quality) apply only to high-risk
|
||||
* systems, so the tile is skipped for limited / minimal / not-applicable classes.
|
||||
*/
|
||||
export function Art10Tile({ riskLevel }: Props) {
|
||||
if (riskLevel !== 'high_risk' && riskLevel !== 'unacceptable') return null
|
||||
|
||||
return (
|
||||
<Link
|
||||
href="/sdk/quality?category=data_quality"
|
||||
className="block mt-3 p-3 rounded-lg border border-purple-200 bg-purple-50 hover:bg-purple-100 transition-colors"
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="w-9 h-9 rounded-full bg-purple-200 text-purple-700 flex items-center justify-center shrink-0">
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
||||
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V7M3 7l9 6 9-6M3 7l9-4 9 4" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-sm font-semibold text-purple-900">
|
||||
Art. 10 Datenqualität (Hochrisiko-KI)
|
||||
</div>
|
||||
<div className="text-xs text-purple-700 mt-0.5">
|
||||
BSI QUAIDAL Controls: 10 Kriterien, 15 Bausteine, 30 Maßnahmen, 140 Metriken.
|
||||
Klicken zum Öffnen des Trainingsdaten-Qualität-Moduls.
|
||||
</div>
|
||||
</div>
|
||||
<svg className="w-4 h-4 text-purple-500 shrink-0 mt-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { RiskPyramid } from './_components/RiskPyramid'
|
||||
import { AddSystemForm } from './_components/AddSystemForm'
|
||||
import { AISystemCard } from './_components/AISystemCard'
|
||||
import DecisionTreeWizard from '@/components/sdk/ai-act/DecisionTreeWizard'
|
||||
import { Art10Tile } from './_components/Art10Tile'
|
||||
|
||||
type TabId = 'overview' | 'decision-tree' | 'results'
|
||||
|
||||
@@ -136,6 +137,7 @@ function SavedResultsTab() {
|
||||
Löschen
|
||||
</button>
|
||||
</div>
|
||||
<Art10Tile riskLevel={r.high_risk_result} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -360,6 +362,16 @@ export default function AIActPage() {
|
||||
)}
|
||||
</StepHeader>
|
||||
|
||||
<div className="px-4 py-2 bg-emerald-50 border border-emerald-200 rounded-lg text-xs text-emerald-800 flex items-start gap-2">
|
||||
<span className="font-semibold">Quellen & Lizenz:</span>
|
||||
<span>
|
||||
Inhalte gemaess <strong>EU-Verordnung 2024/1689 (KI-Verordnung / AI Act)</strong> —
|
||||
Lizenzregel R1 (EU_LAW, woertlich uebernehmbar).
|
||||
Risiko-Klassifizierungslogik basiert auf Anhang III der Verordnung.{' '}
|
||||
<a href="/sdk/licenses" className="underline">Quellenverzeichnis</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="flex items-center gap-1 bg-gray-100 p-1 rounded-lg w-fit">
|
||||
{TABS.map(tab => (
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
CATEGORY_OPTIONS,
|
||||
} from '../control-library/components/helpers'
|
||||
import { ControlDetail } from '../control-library/components/ControlDetail'
|
||||
import { SourceBadge } from '@/components/sdk/SourceBadge'
|
||||
|
||||
// =============================================================================
|
||||
// TYPES
|
||||
@@ -310,6 +311,7 @@ export default function AtomicControlsPage() {
|
||||
<TargetAudienceBadge audience={ctrl.target_audience} />
|
||||
<GenerationStrategyBadge strategy={ctrl.generation_strategy} pipelineInfo={ctrl} />
|
||||
<ObligationTypeBadge type={ctrl.generation_metadata?.obligation_type as string} />
|
||||
<SourceBadge controlUuid={ctrl.id} compact />
|
||||
</div>
|
||||
<h3 className="text-sm font-medium text-gray-900 group-hover:text-violet-700">{ctrl.title}</h3>
|
||||
<p className="text-xs text-gray-500 mt-1 line-clamp-2">{ctrl.objective}</p>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader'
|
||||
import { LicenseModuleBanner } from '@/components/sdk/LicenseModuleBanner'
|
||||
import { useAuditChecklist } from './_hooks/useAuditChecklist'
|
||||
import { ChecklistItemCard } from './_components/ChecklistItemCard'
|
||||
import { LoadingSkeleton } from './_components/LoadingSkeleton'
|
||||
@@ -89,6 +90,12 @@ export default function AuditChecklistPage() {
|
||||
</div>
|
||||
</StepHeader>
|
||||
|
||||
<LicenseModuleBanner
|
||||
rule={3}
|
||||
sourceLabel="BreakPilot-Audit-Methodik"
|
||||
detail="Eigene Audit-Checklisten und -Workflows. Zitierte Rechtsquellen (DSGVO/ISO 27001/...) jeweils mit eigener Lizenzregel."
|
||||
/>
|
||||
|
||||
{error && (
|
||||
<div className="p-4 bg-red-50 border border-red-200 rounded-lg text-red-700 flex items-center justify-between">
|
||||
<span>{error}</span>
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* P107 — Branchen-Benchmark-Cockpit.
|
||||
*
|
||||
* Multi-Site-Vergleich auf einen Blick. Anonymize-Toggle für Big-4-
|
||||
* Wirtschaftspruefer-Demos.
|
||||
*
|
||||
* URL: /sdk/benchmark
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
|
||||
interface Kpi {
|
||||
check_id: string
|
||||
site_label: string
|
||||
site_domain: string
|
||||
captured_at: string
|
||||
industry: string
|
||||
vendors_total: number
|
||||
vendors_us: number
|
||||
vendors_non_eu: number
|
||||
us_pct: number
|
||||
non_eu_pct: number
|
||||
source_breakdown: Record<string, number>
|
||||
max_cookies_per_vendor: number
|
||||
avg_cookies_per_vendor: number
|
||||
cookies_in_browser: number
|
||||
cookies_detailed_count: number
|
||||
cookie_doc_chars: number
|
||||
banner_detected: boolean
|
||||
banner_provider: string
|
||||
banner_violations: number
|
||||
compliance_score: number | null
|
||||
saving_low_eur: number
|
||||
saving_high_eur: number
|
||||
data_quality_pct: number
|
||||
}
|
||||
|
||||
interface Summary {
|
||||
n_sites: number
|
||||
avg_vendors: number
|
||||
avg_us_pct: number
|
||||
avg_non_eu_pct: number
|
||||
avg_cookies_browser: number
|
||||
avg_score: number
|
||||
max_vendors: number
|
||||
max_saving_high: number
|
||||
total_saving_low: number
|
||||
total_saving_high: number
|
||||
}
|
||||
|
||||
const INDUSTRIES = [
|
||||
{ id: '', label: 'Alle Branchen' },
|
||||
{ id: 'automotive', label: 'Automotive (OEM)' },
|
||||
{ id: 'banking', label: 'Banking / Finance' },
|
||||
{ id: 'chemistry', label: 'Chemie / Pharma' },
|
||||
{ id: 'luftfahrt', label: 'Luftfahrt' },
|
||||
{ id: 'ecommerce', label: 'E-Commerce' },
|
||||
{ id: 'saas', label: 'SaaS / Software' },
|
||||
]
|
||||
|
||||
const PRESET_GROUPS = [
|
||||
{ id: 'automotive_oem', label: 'Automotive OEMs', sites: 'Volkswagen,BMW,Mercedes-Benz,SEAT,AUDI' },
|
||||
{ id: 'automotive_supl', label: 'Automotive Zulieferer', sites: 'ZF Friedrichshafen,Robert Bosch,Continental' },
|
||||
{ id: 'chemie', label: 'Chemie (DAX)', sites: 'BASF,Bayer,Henkel,Linde' },
|
||||
{ id: 'luftfahrt', label: 'Luftfahrt', sites: 'Lufthansa,Eurowings,Condor' },
|
||||
{ id: 'banking', label: 'Banking (DAX)', sites: 'Deutsche Bank,Commerzbank,DZ Bank,KfW' },
|
||||
]
|
||||
|
||||
export default function BenchmarkPage() {
|
||||
const [industry, setIndustry] = useState('')
|
||||
const [sites, setSites] = useState('')
|
||||
const [anonymized, setAnonymized] = useState(false)
|
||||
const [data, setData] = useState<{kpis: Kpi[]; summary: Summary} | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true); setError(null)
|
||||
try {
|
||||
const url = new URL('/api/compliance/admin/benchmark', window.location.origin)
|
||||
if (industry) url.searchParams.set('industry', industry)
|
||||
if (sites) url.searchParams.set('sites', sites)
|
||||
if (anonymized) url.searchParams.set('anonymized', 'true')
|
||||
const r = await fetch(url.toString())
|
||||
if (!r.ok) throw new Error(`HTTP ${r.status}`)
|
||||
setData(await r.json())
|
||||
} catch (e: any) {
|
||||
setError(e.message || String(e))
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => { fetchData() }, [])
|
||||
|
||||
return (
|
||||
<div className="p-6 max-w-7xl mx-auto">
|
||||
<header className="mb-6">
|
||||
<h1 className="text-2xl font-bold text-gray-900">
|
||||
Branchen-Benchmark-Cockpit
|
||||
</h1>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
DAX-Konzern-Vergleich auf Basis aller bisher gepruefter Sites.
|
||||
Mit Anonymize-Toggle fuer Wirtschaftspruefer-Demos.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{/* Filter-Leiste */}
|
||||
<div className="bg-white border border-gray-200 rounded-lg p-4 mb-4 flex flex-wrap gap-3 items-end">
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">Branche</label>
|
||||
<select value={industry} onChange={e => setIndustry(e.target.value)}
|
||||
className="px-3 py-2 border rounded text-sm">
|
||||
{INDUSTRIES.map(i => <option key={i.id} value={i.id}>{i.label}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex-1 min-w-[300px]">
|
||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||||
Sites (komma-getrennt) oder Preset wählen
|
||||
</label>
|
||||
<input value={sites} onChange={e => setSites(e.target.value)}
|
||||
placeholder="Volkswagen,BMW,Mercedes-Benz"
|
||||
className="w-full px-3 py-2 border rounded text-sm font-mono" />
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
{PRESET_GROUPS.map(p => (
|
||||
<button key={p.id} onClick={() => setSites(p.sites)}
|
||||
className="px-2 py-0.5 text-[10px] bg-gray-100 hover:bg-gray-200 rounded">
|
||||
{p.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<input type="checkbox" checked={anonymized}
|
||||
onChange={e => setAnonymized(e.target.checked)}
|
||||
className="rounded" />
|
||||
<span><strong>Anonymisieren</strong> (OEM 1/2/3 statt Hersteller-Namen)</span>
|
||||
</label>
|
||||
<button onClick={fetchData} disabled={loading}
|
||||
className="px-4 py-2 bg-purple-600 text-white rounded font-medium hover:bg-purple-700 disabled:opacity-50">
|
||||
{loading ? 'Lade…' : 'Aktualisieren'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 text-red-700 rounded p-3 text-sm mb-4">
|
||||
Fehler: {error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Summary-KPIs */}
|
||||
{data?.summary && (
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-2 mb-4">
|
||||
<Kpi label="Sites im Vergleich" value={data.summary.n_sites} />
|
||||
<Kpi label="⌀ Vendors" value={data.summary.avg_vendors} />
|
||||
<Kpi label="⌀ US-Anteil" value={`${data.summary.avg_us_pct}%`}
|
||||
tone={data.summary.avg_us_pct > 60 ? 'warn' : 'ok'} />
|
||||
<Kpi label="⌀ Score" value={data.summary.avg_score || '—'} />
|
||||
<Kpi label="Saving-Potenzial (Σ)" value={`${Math.round(data.summary.total_saving_high/1000)}k €`}
|
||||
tone="ok" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Vergleichstabelle */}
|
||||
{data?.kpis && data.kpis.length > 0 ? (
|
||||
<div className="bg-white border border-gray-200 rounded-lg overflow-x-auto">
|
||||
<table className="w-full text-xs">
|
||||
<thead className="bg-gray-50 text-gray-700">
|
||||
<tr>
|
||||
<th className="text-left px-3 py-2 sticky left-0 bg-gray-50">Site</th>
|
||||
<th className="text-right px-2 py-2">Score</th>
|
||||
<th className="text-right px-2 py-2">Vendors</th>
|
||||
<th className="text-right px-2 py-2">US%</th>
|
||||
<th className="text-right px-2 py-2">Drittland%</th>
|
||||
<th className="text-right px-2 py-2">Cookies Browser</th>
|
||||
<th className="text-right px-2 py-2">Cookie-Doc kB</th>
|
||||
<th className="text-center px-2 py-2">Banner</th>
|
||||
<th className="text-left px-2 py-2">Provider</th>
|
||||
<th className="text-right px-2 py-2">Banner-Verstöße</th>
|
||||
<th className="text-right px-2 py-2">Saving € Jahr</th>
|
||||
<th className="text-right px-2 py-2">Daten-Qualität</th>
|
||||
<th className="text-left px-2 py-2">Captured</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.kpis.map((k, i) => (
|
||||
<tr key={i} className={`border-t hover:bg-gray-50 ${i%2 ? 'bg-gray-50/30' : ''}`}>
|
||||
<td className="px-3 py-2 font-semibold sticky left-0 bg-inherit">
|
||||
{k.site_label}
|
||||
<div className="text-[9px] text-gray-400 font-mono">{k.check_id}</div>
|
||||
</td>
|
||||
<td className={`px-2 py-2 text-right ${
|
||||
!k.compliance_score ? 'text-gray-400' :
|
||||
k.compliance_score >= 80 ? 'text-green-700' :
|
||||
k.compliance_score >= 60 ? 'text-amber-700' : 'text-red-700'
|
||||
}`}>
|
||||
{k.compliance_score ?? '—'}
|
||||
</td>
|
||||
<td className="px-2 py-2 text-right font-mono">{k.vendors_total}</td>
|
||||
<td className={`px-2 py-2 text-right ${k.us_pct > 60 ? 'text-red-700 font-semibold' : ''}`}>
|
||||
{k.us_pct}%
|
||||
</td>
|
||||
<td className={`px-2 py-2 text-right ${k.non_eu_pct > 70 ? 'text-red-700' : ''}`}>
|
||||
{k.non_eu_pct}%
|
||||
</td>
|
||||
<td className="px-2 py-2 text-right font-mono">{k.cookies_in_browser}</td>
|
||||
<td className="px-2 py-2 text-right text-gray-500">
|
||||
{Math.round(k.cookie_doc_chars / 1000)}k
|
||||
</td>
|
||||
<td className="px-2 py-2 text-center">{k.banner_detected ? '✓' : '✗'}</td>
|
||||
<td className="px-2 py-2 text-gray-600">{k.banner_provider || '—'}</td>
|
||||
<td className={`px-2 py-2 text-right ${k.banner_violations ? 'text-red-700' : 'text-gray-400'}`}>
|
||||
{k.banner_violations || 0}
|
||||
</td>
|
||||
<td className="px-2 py-2 text-right text-green-700 font-mono">
|
||||
{k.saving_high_eur ? `${(k.saving_high_eur/1000).toFixed(0)}k` : '—'}
|
||||
</td>
|
||||
<td className={`px-2 py-2 text-right ${
|
||||
k.data_quality_pct >= 70 ? 'text-green-700' :
|
||||
k.data_quality_pct >= 40 ? 'text-amber-700' : 'text-red-700'
|
||||
}`}>
|
||||
{k.data_quality_pct}%
|
||||
</td>
|
||||
<td className="px-2 py-2 text-[10px] text-gray-500">
|
||||
{k.captured_at?.substring(0, 16).replace('T', ' ')}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : !loading && (
|
||||
<div className="bg-gray-50 border border-gray-200 rounded-lg p-8 text-center text-gray-500">
|
||||
Keine Snapshots gefunden — Filter anpassen oder einen Audit-Lauf starten.
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-4 text-xs text-gray-500">
|
||||
<strong>Big-4-Hinweis:</strong> Mit Anonymize-Toggle koennen wir den
|
||||
kompletten Branchen-Cut zeigen ohne Hersteller-Namen zu nennen
|
||||
(z.B. "OEM 3 hat 78% US-Vendor-Anteil"). Damit ist die Daten-
|
||||
Hoheit bei BreakPilot und Big 4 sieht den Mehrwert ohne dass
|
||||
Wettbewerber-Vergleiche extern werden.
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Kpi({ label, value, tone = 'neutral' }: {
|
||||
label: string; value: any; tone?: 'ok' | 'warn' | 'bad' | 'neutral'
|
||||
}) {
|
||||
const colors: Record<string, string> = {
|
||||
ok: 'text-green-700 bg-green-50 border-green-200',
|
||||
warn: 'text-amber-700 bg-amber-50 border-amber-200',
|
||||
bad: 'text-red-700 bg-red-50 border-red-200',
|
||||
neutral: 'text-gray-700 bg-white border-gray-200',
|
||||
}
|
||||
return (
|
||||
<div className={`border rounded p-3 ${colors[tone]}`}>
|
||||
<div className="text-[10px] uppercase tracking-wider opacity-70">{label}</div>
|
||||
<div className="text-xl font-bold mt-1">{value}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -232,14 +232,25 @@ export function StateBadge({ state }: { state: string }) {
|
||||
|
||||
export function LicenseRuleBadge({ rule }: { rule: number | null | undefined }) {
|
||||
if (!rule) return null
|
||||
const config: Record<number, { bg: string; label: string }> = {
|
||||
1: { bg: 'bg-green-100 text-green-700', label: 'Free Use' },
|
||||
2: { bg: 'bg-blue-100 text-blue-700', label: 'Zitation' },
|
||||
3: { bg: 'bg-amber-100 text-amber-700', label: 'Reformuliert' },
|
||||
// Corrected labels per Task #21 LICENSE_RULES.md mapping:
|
||||
// R1 = woertlich (Hoheitsrecht/Public Domain, no attribution required)
|
||||
// R2 = woertlich + Attribution-Pflicht (CC-BY, OWASP, OECD, ENISA)
|
||||
// R3 = nur Identifier zitieren (DIN/ANSI/IEC/DGUV/proprietary — pipeline drops full text)
|
||||
const config: Record<number, { bg: string; label: string; title: string }> = {
|
||||
1: { bg: 'bg-emerald-100 text-emerald-800', label: 'R1', title: 'Woertlich uebernehmbar (Hoheitsrecht/Public Domain)' },
|
||||
2: { bg: 'bg-amber-100 text-amber-800', label: 'R2', title: 'Woertlich mit Attribution (CC-BY/OWASP/OECD/ENISA)' },
|
||||
3: { bg: 'bg-slate-100 text-slate-700', label: 'R3', title: 'Nur Identifier-Verweis (DIN/ANSI/IEC/proprietaer)' },
|
||||
}
|
||||
const c = config[rule]
|
||||
if (!c) return null
|
||||
return <span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${c.bg}`}>{c.label}</span>
|
||||
return (
|
||||
<span
|
||||
className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${c.bg}`}
|
||||
title={c.title}
|
||||
>
|
||||
{c.label}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export function VerificationMethodBadge({ method }: { method: string | null }) {
|
||||
|
||||
@@ -99,6 +99,16 @@ export default function CRAProjectsPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-4 px-4 py-2 bg-emerald-50 border border-emerald-200 rounded-lg text-xs text-emerald-800 flex items-start gap-2">
|
||||
<span className="font-semibold">Quellen & Lizenz:</span>
|
||||
<span>
|
||||
Inhalte gemaess <strong>EU-Verordnung 2024/2847 (Cyber Resilience Act)</strong> —
|
||||
Lizenzregel R1 (EU_LAW, woertlich uebernehmbar). ENISA-Implementation-Guidance
|
||||
ergaenzend (R1 EU_PUBLIC).{' '}
|
||||
<a href="/sdk/licenses" className="underline">Quellenverzeichnis</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="mb-4 bg-red-50 border border-red-200 rounded-lg p-4 text-sm text-red-700">
|
||||
{error}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* Lifecycle-Phasen-Filter für den Document-Generator.
|
||||
*
|
||||
* Zeigt 5 Phasen-Tabs (Pre-Founding, Founding, Startup, KMU, Konzern) und
|
||||
* filtert die angezeigten Templates entsprechend ihres `lifecycle_stage`-Arrays.
|
||||
*
|
||||
* Phasen-Definitionen synchron zu lib/sdk/founding/template-categories.ts
|
||||
*/
|
||||
|
||||
import {
|
||||
LIFECYCLE_STAGE_LABELS,
|
||||
type LifecycleStage,
|
||||
TEMPLATE_CATEGORIES,
|
||||
} from '@/lib/sdk/founding/template-categories'
|
||||
|
||||
interface Props {
|
||||
activeStage: LifecycleStage | 'all'
|
||||
onChange: (stage: LifecycleStage | 'all') => void
|
||||
/** Template-Counts pro Stage (optional, sonst aus Code-Registry berechnet) */
|
||||
countsByStage?: Record<string, number>
|
||||
}
|
||||
|
||||
const STAGE_ORDER: (LifecycleStage | 'all')[] = [
|
||||
'all',
|
||||
'pre_founding',
|
||||
'founding',
|
||||
'startup',
|
||||
'kmu',
|
||||
'konzern',
|
||||
]
|
||||
|
||||
const STAGE_ICONS: Record<LifecycleStage | 'all', string> = {
|
||||
all: '📚',
|
||||
pre_founding: '🌱',
|
||||
founding: '⚖️',
|
||||
startup: '🚀',
|
||||
kmu: '🏢',
|
||||
konzern: '🏛️',
|
||||
}
|
||||
|
||||
const STAGE_HINTS: Record<LifecycleStage, string> = {
|
||||
pre_founding: 'Vor dem Notartermin — Term Sheet, IP-Sicherung, Wandeldarlehen',
|
||||
founding: 'Für den Notartermin — Satzung, Gesellschafterliste, HRB-Anmeldung',
|
||||
startup: '0–3 Jahre, <25 Mitarbeiter — Arbeitsverträge, AVV, Datenschutz',
|
||||
kmu: '3+ Jahre, 25–250 MA — ISMS, Whistleblower, vollständige TOM',
|
||||
konzern: '250+ MA — Konzern-Compliance, ISO 27001',
|
||||
}
|
||||
|
||||
export function LifecycleFilter({ activeStage, onChange, countsByStage }: Props) {
|
||||
const counts = countsByStage || computeCountsFromRegistry()
|
||||
|
||||
return (
|
||||
<div className="mb-6" data-testid="lifecycle-filter">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<h3 className="text-sm font-semibold text-gray-700">Phase Deines Unternehmens</h3>
|
||||
<span className="text-xs text-gray-500">— filtert Dokumente nach Lifecycle</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{STAGE_ORDER.map(stage => {
|
||||
const isAll = stage === 'all'
|
||||
const count = isAll
|
||||
? Object.values(counts).reduce((s, c) => s + c, 0)
|
||||
: (counts[stage] || 0)
|
||||
const label = isAll ? 'Alle' : LIFECYCLE_STAGE_LABELS[stage as LifecycleStage].split(' (')[0]
|
||||
const isActive = activeStage === stage
|
||||
return (
|
||||
<button
|
||||
key={stage}
|
||||
type="button"
|
||||
data-testid={`stage-tab-${stage}`}
|
||||
onClick={() => onChange(stage)}
|
||||
className={`px-3 py-2 rounded-lg border text-sm font-medium transition ${
|
||||
isActive
|
||||
? 'bg-purple-600 text-white border-purple-600 shadow-sm'
|
||||
: 'bg-white text-gray-700 border-gray-200 hover:border-purple-300 hover:bg-purple-50'
|
||||
}`}
|
||||
>
|
||||
<span className="mr-1.5">{STAGE_ICONS[stage]}</span>
|
||||
{label}
|
||||
<span className={`ml-2 px-1.5 py-0.5 text-xs rounded-full ${
|
||||
isActive ? 'bg-white/20' : 'bg-gray-100 text-gray-600'
|
||||
}`}>
|
||||
{count}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{activeStage !== 'all' && (
|
||||
<p className="mt-2 text-sm text-gray-500" data-testid="stage-hint">
|
||||
{STAGE_HINTS[activeStage as LifecycleStage]}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function computeCountsFromRegistry(): Record<string, number> {
|
||||
const counts: Record<string, number> = {
|
||||
pre_founding: 0, founding: 0, startup: 0, kmu: 0, konzern: 0,
|
||||
}
|
||||
for (const cat of Object.values(TEMPLATE_CATEGORIES)) {
|
||||
for (const stage of cat.lifecycle_stage) {
|
||||
counts[stage] = (counts[stage] || 0) + 1
|
||||
}
|
||||
}
|
||||
return counts
|
||||
}
|
||||
|
||||
export function filterTemplatesByStage<T extends { document_type?: string; type?: string }>(
|
||||
templates: T[],
|
||||
stage: LifecycleStage | 'all'
|
||||
): T[] {
|
||||
if (stage === 'all') return templates
|
||||
return templates.filter(t => {
|
||||
const docType = t.document_type || t.type
|
||||
if (!docType) return false
|
||||
const cat = TEMPLATE_CATEGORIES[docType]
|
||||
if (!cat) return stage === 'startup' // Fallback: unkategorisierte zeigen wir in Startup
|
||||
return cat.lifecycle_stage.includes(stage)
|
||||
})
|
||||
}
|
||||
@@ -39,7 +39,7 @@ export const CATEGORIES: { key: string; label: string; types: string[] | null }[
|
||||
]},
|
||||
|
||||
// Datenschutz-Informationen (alle DSI-Typen):
|
||||
{ key: 'dsi', label: 'Datenschutzinfos', types: ['privacy_policy', 'applicant_dsi', 'employee_dsi', 'social_media_dsi', 'video_conference_dsi', 'informationspflichten'] },
|
||||
{ key: 'dsi', label: 'Datenschutzinfos', types: ['privacy_policy', 'data_protection_policy', 'applicant_dsi', 'employee_dsi', 'social_media_dsi', 'video_conference_dsi', 'informationspflichten'] },
|
||||
|
||||
// Einwilligungen:
|
||||
{ key: 'consent', label: 'Einwilligungen', types: ['consent_texts', 'cookie_banner', 'verpflichtungserklaerung'] },
|
||||
|
||||
@@ -15,6 +15,8 @@ import { getGeneratorDefaults, getProfileLabel } from './scopeDefaults'
|
||||
import TemplateLibrary from './_components/TemplateLibrary'
|
||||
import GeneratorSection from './_components/GeneratorSection'
|
||||
import RecommendedDocuments from './_components/RecommendedDocuments'
|
||||
import { LifecycleFilter, filterTemplatesByStage } from './_components/LifecycleFilter'
|
||||
import type { LifecycleStage } from '@/lib/sdk/founding/template-categories'
|
||||
|
||||
function DocumentGeneratorPageInner() {
|
||||
const { state } = useSDK()
|
||||
@@ -24,6 +26,7 @@ function DocumentGeneratorPageInner() {
|
||||
const [allTemplates, setAllTemplates] = useState<LegalTemplateResult[]>([])
|
||||
const [isLoadingLibrary, setIsLoadingLibrary] = useState(true)
|
||||
const [activeCategory, setActiveCategory] = useState<string>('all')
|
||||
const [activeStage, setActiveStage] = useState<LifecycleStage | 'all'>('all')
|
||||
const [activeLanguage, setActiveLanguage] = useState<'all' | 'de' | 'en'>('all')
|
||||
const [librarySearch, setLibrarySearch] = useState('')
|
||||
const [expandedPreviewId, setExpandedPreviewId] = useState<string | null>(null)
|
||||
@@ -209,10 +212,15 @@ function DocumentGeneratorPageInner() {
|
||||
}
|
||||
}, [selectedDataPointsData])
|
||||
|
||||
// Filtered templates (computed)
|
||||
// Filtered templates (computed) — Lifecycle + Category + Language + Search
|
||||
const filteredTemplates = useMemo(() => {
|
||||
const category = CATEGORIES.find((c: { key: string }) => c.key === activeCategory)
|
||||
return allTemplates.filter((t) => {
|
||||
// 1. Lifecycle-Phase Filter via Code-Registry (mapped auf templateType)
|
||||
const stageFiltered = filterTemplatesByStage(
|
||||
allTemplates.map(t => ({ ...t, document_type: t.templateType || '' })),
|
||||
activeStage
|
||||
)
|
||||
return stageFiltered.filter((t) => {
|
||||
if (category && category.types !== null) {
|
||||
if (!category.types.includes(t.templateType || '')) return false
|
||||
}
|
||||
@@ -225,7 +233,22 @@ function DocumentGeneratorPageInner() {
|
||||
}
|
||||
return true
|
||||
})
|
||||
}, [allTemplates, activeCategory, activeLanguage, librarySearch])
|
||||
}, [allTemplates, activeCategory, activeStage, activeLanguage, librarySearch])
|
||||
|
||||
// Counts by stage for filter UI
|
||||
const countsByStage = useMemo(() => {
|
||||
const counts: Record<string, number> = { pre_founding: 0, founding: 0, startup: 0, kmu: 0, konzern: 0 }
|
||||
const stages: LifecycleStage[] = ['pre_founding', 'founding', 'startup', 'kmu', 'konzern']
|
||||
for (const t of allTemplates) {
|
||||
const docType = t.templateType || ''
|
||||
for (const s of stages) {
|
||||
if (filterTemplatesByStage([{ document_type: docType }], s).length) {
|
||||
counts[s]++
|
||||
}
|
||||
}
|
||||
}
|
||||
return counts
|
||||
}, [allTemplates])
|
||||
|
||||
const handleUseTemplate = useCallback((t: LegalTemplateResult) => {
|
||||
setActiveTemplate(t)
|
||||
@@ -274,6 +297,16 @@ function DocumentGeneratorPageInner() {
|
||||
tips={stepInfo.tips}
|
||||
/>
|
||||
|
||||
<div className="px-4 py-2 bg-slate-50 border border-slate-200 rounded-lg text-xs text-slate-700 flex items-start gap-2">
|
||||
<span className="font-semibold">Quellen & Lizenz:</span>
|
||||
<span>
|
||||
Die 91 Standard-Vorlagen sind <strong>BreakPilot-Eigenwerke</strong> (Lizenzregel R3 — Identifier-Verweis,
|
||||
eigene Lizenz). Vorlagen mit gesetzlicher Grundlage (z.B. VVT nach Art. 30 DSGVO,
|
||||
Loeschkonzept nach Art. 17 DSGVO) zitieren die jeweilige Rechtsquelle als R1.{' '}
|
||||
<a href="/sdk/licenses" className="underline">Quellenverzeichnis</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Status bar */}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-5">
|
||||
@@ -292,6 +325,13 @@ function DocumentGeneratorPageInner() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Lifecycle-Phase Filter */}
|
||||
<LifecycleFilter
|
||||
activeStage={activeStage}
|
||||
onChange={setActiveStage}
|
||||
countsByStage={countsByStage}
|
||||
/>
|
||||
|
||||
{/* Recommended documents based on scope profile */}
|
||||
<RecommendedDocuments
|
||||
allTemplates={allTemplates}
|
||||
|
||||
@@ -225,6 +225,51 @@ const TEMPLATE_RULES: TemplateRule[] = [
|
||||
condition: () => 'required', // Immer Pflicht bei Websites
|
||||
},
|
||||
|
||||
// ── DSE & Datenschutz-Kerndokumente (P38) ──────────────────────────────
|
||||
{
|
||||
templateType: 'privacy_policy',
|
||||
label: 'Datenschutzerklaerung (Website)',
|
||||
condition: () => 'required', // Art. 13 DSGVO — bei jeder Website Pflicht
|
||||
},
|
||||
{
|
||||
templateType: 'data_protection_policy',
|
||||
label: 'Datenschutzrichtlinie (intern)',
|
||||
condition: (_answers, level) => level >= 'L2' ? 'required' : 'recommended',
|
||||
},
|
||||
{
|
||||
templateType: 'dsfa',
|
||||
label: 'DSFA-Vorlage',
|
||||
condition: (answers) => {
|
||||
const dsfa = answers.get('proc_dsfa_required') || answers.get('comp_dsfa_processes')
|
||||
if (dsfa === 'yes' || dsfa === 'required') return 'required'
|
||||
return 'optional'
|
||||
},
|
||||
},
|
||||
{
|
||||
templateType: 'dpa',
|
||||
label: 'Auftragsverarbeitungsvertrag (AVV)',
|
||||
condition: (answers) => {
|
||||
const vendors = answers.get('comp_has_processors') || answers.get('comp_vendor_management')
|
||||
if (vendors && vendors !== 'no') return 'required'
|
||||
return 'recommended'
|
||||
},
|
||||
},
|
||||
{
|
||||
templateType: 'vvt_register',
|
||||
label: 'Verzeichnis von Verarbeitungstaetigkeiten (VVT)',
|
||||
condition: (_answers, level) => level >= 'L2' ? 'required' : 'recommended',
|
||||
},
|
||||
{
|
||||
templateType: 'tom_documentation',
|
||||
label: 'TOM-Dokumentation',
|
||||
condition: (_answers, level) => level >= 'L2' ? 'required' : 'recommended',
|
||||
},
|
||||
{
|
||||
templateType: 'loeschkonzept',
|
||||
label: 'Loeschkonzept',
|
||||
condition: (_answers, level) => level >= 'L2' ? 'required' : 'recommended',
|
||||
},
|
||||
|
||||
// ── Drittlandtransfer (SCC + TIA) ───────────────────────────────────────
|
||||
// SCC+TIA nur erforderlich wenn Drittlandtransfer OHNE Angemessenheitsbeschluss/DPF
|
||||
{
|
||||
|
||||
@@ -132,6 +132,16 @@ export default function DSFAPage() {
|
||||
)}
|
||||
</StepHeader>
|
||||
|
||||
<div className="px-4 py-2 bg-emerald-50 border border-emerald-200 rounded-lg text-xs text-emerald-800 flex items-start gap-2">
|
||||
<span className="font-semibold">Quellen & Lizenz:</span>
|
||||
<span>
|
||||
Inhalte gemaess <strong>DSGVO Art. 35</strong> (EU 2016/679) — Lizenzregel R1
|
||||
(Hoheitsrecht/EU_LAW, woertlich uebernehmbar). Vorlagen-Texte aus
|
||||
Aufsichtsbehoerden ebenfalls R1.{' '}
|
||||
<a href="/sdk/licenses" className="underline">Quellenverzeichnis</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* DSFA Requirement Check */}
|
||||
{dsfaCheck.required && dsfas.length === 0 && (
|
||||
<div className="bg-red-50 border border-red-200 rounded-xl p-5">
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import type { FoundingWizardState } from '@/lib/sdk/founding/types'
|
||||
|
||||
interface Props {
|
||||
state: FoundingWizardState
|
||||
update: <K extends keyof FoundingWizardState>(k: K, v: FoundingWizardState[K]) => void
|
||||
}
|
||||
|
||||
export function StepBasics({ state, update }: Props) {
|
||||
const b = state.basics
|
||||
const [prefillStatus, setPrefillStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle')
|
||||
|
||||
async function prefillFromCompanyProfile() {
|
||||
setPrefillStatus('loading')
|
||||
try {
|
||||
const res = await fetch('/api/sdk/v1/company-profile', { cache: 'no-store' })
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
const payload = await res.json()
|
||||
const p = payload?.profile ?? payload
|
||||
if (!p || typeof p !== 'object') throw new Error('leeres Profil')
|
||||
const industries = Array.isArray(p.industry) ? p.industry.filter(Boolean) : []
|
||||
const industry = industries.length > 0
|
||||
? industries.join(', ')
|
||||
: (p.industryOther || b.industry)
|
||||
const address = [p.headquartersStreet, [p.headquartersZip, p.headquartersCity].filter(Boolean).join(' ')]
|
||||
.filter(Boolean).join(', ') || b.company_address
|
||||
const seat = p.headquartersCity || b.company_seat
|
||||
// Purpose ableiten aus offerings/businessModel — Fallback wenn nichts da
|
||||
const purposeBits: string[] = []
|
||||
if (p.businessModel) purposeBits.push(`Geschäftsmodell: ${p.businessModel}`)
|
||||
if (Array.isArray(p.offerings) && p.offerings.length > 0)
|
||||
purposeBits.push(`Leistungen: ${p.offerings.join(', ')}`)
|
||||
const purpose = purposeBits.length > 0
|
||||
? purposeBits.join('; ')
|
||||
: b.company_purpose_description
|
||||
update('basics', {
|
||||
...b,
|
||||
company_name: p.companyName || b.company_name,
|
||||
legal_form: (p.legalForm === 'UG' ? 'UG' : (p.legalForm === 'GmbH' ? 'GmbH' : b.legal_form)),
|
||||
company_seat: seat,
|
||||
company_address: address,
|
||||
industry,
|
||||
company_purpose_description: b.company_purpose_description.trim() === '' ? purpose : b.company_purpose_description,
|
||||
})
|
||||
setPrefillStatus('success')
|
||||
} catch (err) {
|
||||
console.error('[founding-wizard] prefill failed', err)
|
||||
setPrefillStatus('error')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-gray-600">
|
||||
Stammdaten der Gesellschaft. Pflicht für Satzung, HRB-Anmeldung und SHA.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={prefillFromCompanyProfile}
|
||||
disabled={prefillStatus === 'loading'}
|
||||
className="px-3 py-1.5 text-sm rounded-lg border border-blue-300 bg-blue-50 hover:bg-blue-100 disabled:opacity-50"
|
||||
>
|
||||
{prefillStatus === 'loading' ? 'Lade…' : 'Aus Unternehmensprofil vorbefüllen'}
|
||||
</button>
|
||||
</div>
|
||||
{prefillStatus === 'success' && (
|
||||
<div className="text-xs text-green-700 bg-green-50 border border-green-200 rounded px-2 py-1">
|
||||
Daten aus Unternehmensprofil übernommen. Bitte prüfen und ergänzen.
|
||||
</div>
|
||||
)}
|
||||
{prefillStatus === 'error' && (
|
||||
<div className="text-xs text-amber-700 bg-amber-50 border border-amber-200 rounded px-2 py-1">
|
||||
Konnte Unternehmensprofil nicht laden — bitte Felder manuell ausfüllen.
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Firmenname</label>
|
||||
<input
|
||||
data-testid="company-name"
|
||||
type="text"
|
||||
value={b.company_name}
|
||||
onChange={e => update('basics', { ...b, company_name: e.target.value })}
|
||||
placeholder="Breakpilot GmbH"
|
||||
className="w-full px-3 py-2 border rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Rechtsform</label>
|
||||
<select
|
||||
data-testid="legal-form"
|
||||
value={b.legal_form}
|
||||
onChange={e => update('basics', { ...b, legal_form: e.target.value as 'GmbH' | 'UG' })}
|
||||
className="w-full px-3 py-2 border rounded-lg"
|
||||
>
|
||||
<option value="GmbH">GmbH</option>
|
||||
<option value="UG">UG (haftungsbeschränkt)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Sitz (Stadt)</label>
|
||||
<input
|
||||
data-testid="company-seat"
|
||||
type="text"
|
||||
value={b.company_seat}
|
||||
onChange={e => update('basics', { ...b, company_seat: e.target.value })}
|
||||
placeholder="z.B. Stuttgart"
|
||||
className="w-full px-3 py-2 border rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Adresse</label>
|
||||
<input
|
||||
data-testid="company-address"
|
||||
type="text"
|
||||
value={b.company_address}
|
||||
onChange={e => update('basics', { ...b, company_address: e.target.value })}
|
||||
placeholder="Straße, PLZ Ort"
|
||||
className="w-full px-3 py-2 border rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Branche</label>
|
||||
<input
|
||||
data-testid="industry"
|
||||
type="text"
|
||||
value={b.industry}
|
||||
onChange={e => update('basics', { ...b, industry: e.target.value })}
|
||||
placeholder="z.B. SaaS, Beratung, Handwerk"
|
||||
className="w-full px-3 py-2 border rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Geschäftsjahr</label>
|
||||
<input
|
||||
data-testid="business-year"
|
||||
type="text"
|
||||
value={b.business_year}
|
||||
onChange={e => update('basics', { ...b, business_year: e.target.value })}
|
||||
className="w-full px-3 py-2 border rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Registergericht
|
||||
</label>
|
||||
<input
|
||||
data-testid="register-court"
|
||||
type="text"
|
||||
value={b.register_court || ''}
|
||||
onChange={e => update('basics', { ...b, register_court: e.target.value })}
|
||||
placeholder="z.B. Amtsgericht Stuttgart"
|
||||
className="w-full px-3 py-2 border rounded-lg"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Zuständiges Amtsgericht für HRB-Eintragung
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
HRB-Nummer <span className="text-gray-400">(optional)</span>
|
||||
</label>
|
||||
<input
|
||||
data-testid="hrb-number"
|
||||
type="text"
|
||||
value={b.hrb_number || ''}
|
||||
onChange={e => update('basics', { ...b, hrb_number: e.target.value })}
|
||||
placeholder="z.B. HRB 12345 (leer falls noch nicht eingetragen)"
|
||||
className="w-full px-3 py-2 border rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Unternehmensgegenstand (Volltext für § 2 Satzung)
|
||||
</label>
|
||||
<textarea
|
||||
data-testid="company-purpose"
|
||||
value={b.company_purpose_description}
|
||||
onChange={e => update('basics', { ...b, company_purpose_description: e.target.value })}
|
||||
rows={4}
|
||||
placeholder="z.B. die Entwicklung, Bereitstellung, der Betrieb und der Vertrieb von Softwarelösungen, Plattformen und IT-Dienstleistungen im Bereich der Künstlichen Intelligenz"
|
||||
className="w-full px-3 py-2 border rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Detaillierte Tätigkeitsbereiche (eine Zeile pro Bullet)
|
||||
</label>
|
||||
<textarea
|
||||
data-testid="company-purpose-bullets"
|
||||
value={b.company_purpose_bullets.join('\n')}
|
||||
onChange={e => update('basics', { ...b, company_purpose_bullets: e.target.value.split('\n').filter(Boolean) })}
|
||||
rows={5}
|
||||
placeholder={'a) Entwicklung von Software\nb) Beratung im Bereich...\nc) ...'}
|
||||
className="w-full px-3 py-2 border rounded-lg font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="research_focus"
|
||||
data-testid="research-focus"
|
||||
checked={b.has_research_focus}
|
||||
onChange={e => update('basics', { ...b, has_research_focus: e.target.checked })}
|
||||
/>
|
||||
<label htmlFor="research_focus" className="text-sm text-gray-700">
|
||||
Forschungsfokus (aktiviert F&E-Klauseln in SHA und GO-GF)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
'use client'
|
||||
|
||||
import { useMemo } from 'react'
|
||||
import type { FoundingWizardState, GeneratedDocument } from '@/lib/sdk/founding/types'
|
||||
import { NOTARY_BUNDLE_DOCUMENTS } from '@/lib/sdk/founding/template-categories'
|
||||
|
||||
interface Props {
|
||||
state: FoundingWizardState
|
||||
update: <K extends keyof FoundingWizardState>(k: K, v: FoundingWizardState[K]) => void
|
||||
generating: boolean
|
||||
error: string | null
|
||||
onGenerate: () => Promise<GeneratedDocument[]>
|
||||
}
|
||||
|
||||
const DOC_LABELS: Record<string, string> = {
|
||||
articles_of_association: 'Satzung',
|
||||
gesellschafterliste: 'Gesellschafterliste (§ 40 GmbHG)',
|
||||
gf_bestellungsbeschluss: 'Gesellschafterbeschluss zur GF-Bestellung',
|
||||
hrb_anmeldung: 'Handelsregister-Anmeldung',
|
||||
sha: 'Shareholders\' Agreement (SHA)',
|
||||
geschaeftsordnung_gf: 'Geschäftsordnung Geschäftsführung (GO-GF)',
|
||||
managing_director_employment_contract: 'GF-Dienstvertrag (pro GF)',
|
||||
ip_assignment_agreement: 'IP-Assignment (pro Gründer)',
|
||||
term_sheet: 'Term Sheet',
|
||||
convertible_loan_agreement: 'Wandeldarlehensvertrag',
|
||||
subscription_agreement: 'Beteiligungsvertrag',
|
||||
esop_plan: 'ESOP/VSOP-Plan',
|
||||
cap_table: 'Cap Table',
|
||||
}
|
||||
|
||||
export function StepGenerate({ state, update, generating, error, onGenerate }: Props) {
|
||||
const toggleDoc = (docType: string) => {
|
||||
const next = state.selected_documents.includes(docType)
|
||||
? state.selected_documents.filter(d => d !== docType)
|
||||
: [...state.selected_documents, docType]
|
||||
update('selected_documents', next)
|
||||
}
|
||||
|
||||
const selectNotaryBundle = () => {
|
||||
update('selected_documents', [...NOTARY_BUNDLE_DOCUMENTS])
|
||||
}
|
||||
|
||||
const summary = useMemo(() => ({
|
||||
name: state.basics.company_name,
|
||||
seat: state.basics.company_seat,
|
||||
stammkapital: state.capital.stammkapital_eur,
|
||||
num_gesellschafter: state.gesellschafter.length,
|
||||
num_gf: state.gesellschafter.filter(g => g.is_geschaeftsfuehrer).length,
|
||||
}), [state])
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="bg-purple-50 border border-purple-200 rounded-lg p-4">
|
||||
<h3 className="font-semibold text-purple-900 mb-2">Zusammenfassung</h3>
|
||||
<dl className="grid grid-cols-2 gap-2 text-sm" data-testid="generate-summary">
|
||||
<dt className="text-gray-600">Firma:</dt><dd>{summary.name} ({state.basics.legal_form})</dd>
|
||||
<dt className="text-gray-600">Sitz:</dt><dd>{summary.seat}</dd>
|
||||
<dt className="text-gray-600">Stammkapital:</dt><dd>{summary.stammkapital.toLocaleString('de-DE')} €</dd>
|
||||
<dt className="text-gray-600">Gesellschafter:</dt><dd>{summary.num_gesellschafter}</dd>
|
||||
<dt className="text-gray-600">Geschäftsführer:</dt><dd>{summary.num_gf}</dd>
|
||||
<dt className="text-gray-600">Notar:</dt><dd>{state.notar.notary_name} ({state.notar.notary_place})</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<h3 className="font-semibold">Zu generierende Dokumente</h3>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="select-notary-bundle"
|
||||
onClick={selectNotaryBundle}
|
||||
className="text-sm text-purple-600 hover:underline"
|
||||
>
|
||||
➜ Notartermin-Bundle auswählen
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
{Object.entries(DOC_LABELS).map(([docType, label]) => (
|
||||
<label key={docType} className="flex items-start gap-3 p-2 hover:bg-gray-50 rounded">
|
||||
<input
|
||||
type="checkbox"
|
||||
data-testid={`doc-${docType}`}
|
||||
checked={state.selected_documents.includes(docType)}
|
||||
onChange={() => toggleDoc(docType)}
|
||||
className="mt-1"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<div className="text-sm font-medium">{label}</div>
|
||||
<div className="text-xs text-gray-500">{docType}</div>
|
||||
</div>
|
||||
{NOTARY_BUNDLE_DOCUMENTS.includes(docType) && (
|
||||
<span className="text-xs bg-purple-100 text-purple-700 px-2 py-0.5 rounded">Notartermin</span>
|
||||
)}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center pt-4 border-t">
|
||||
<p className="text-sm text-gray-500">
|
||||
{state.selected_documents.length} Dokument(e) ausgewählt
|
||||
</p>
|
||||
<button
|
||||
data-testid="generate-docs"
|
||||
onClick={onGenerate}
|
||||
disabled={generating || state.selected_documents.length === 0}
|
||||
className="px-6 py-3 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50 font-medium"
|
||||
>
|
||||
{generating ? 'Generiere...' : 'Dokumente als Word generieren'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-3 text-sm text-red-900" data-testid="generate-error">
|
||||
Fehler: {error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{state.generated_documents && state.generated_documents.length > 0 && (
|
||||
<div className="bg-green-50 border border-green-200 rounded-lg p-4" data-testid="generated-docs">
|
||||
<h3 className="font-semibold text-green-900 mb-3">
|
||||
✓ {state.generated_documents.length} Dokument(e) generiert
|
||||
</h3>
|
||||
<ul className="space-y-2">
|
||||
{state.generated_documents.map((doc, idx) => (
|
||||
<li key={idx} className="flex justify-between items-center bg-white rounded px-3 py-2 border border-green-200">
|
||||
<div>
|
||||
<div className="text-sm font-medium">{doc.title}</div>
|
||||
<div className="text-xs text-gray-500">{(doc.size_bytes / 1024).toFixed(1)} KB</div>
|
||||
</div>
|
||||
<a
|
||||
href={doc.download_url}
|
||||
download
|
||||
data-testid={`download-${doc.document_type}`}
|
||||
className="px-3 py-1.5 bg-green-600 text-white rounded text-sm hover:bg-green-700"
|
||||
>
|
||||
Word herunterladen
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import type { FoundingWizardState, Gesellschafter } from '@/lib/sdk/founding/types'
|
||||
|
||||
interface Props {
|
||||
state: FoundingWizardState
|
||||
addGesellschafter: (g: Omit<Gesellschafter, 'id' | 'anteil_nr'>) => void
|
||||
updateGesellschafter: (id: string, p: Partial<Gesellschafter>) => void
|
||||
removeGesellschafter: (id: string) => void
|
||||
}
|
||||
|
||||
export function StepGesellschafter({ state, addGesellschafter, updateGesellschafter, removeGesellschafter }: Props) {
|
||||
const [form, setForm] = useState({
|
||||
name: '', geburtsdatum: '', adresse: '', email: '',
|
||||
nennbetrag_eur: 12500, is_geschaeftsfuehrer: true, internal_role: '',
|
||||
has_academic_background: false, ip_areas: '',
|
||||
})
|
||||
|
||||
const totalNennbetrag = state.gesellschafter.reduce((s, g) => s + g.nennbetrag_eur, 0)
|
||||
const target = state.capital.stammkapital_eur
|
||||
|
||||
const handleAdd = () => {
|
||||
if (!form.name.trim()) return
|
||||
const ip_areas = form.ip_areas
|
||||
.split('\n').map(s => s.trim()).filter(Boolean)
|
||||
addGesellschafter({
|
||||
rolle: 'founder',
|
||||
name: form.name,
|
||||
geburtsdatum: form.geburtsdatum || undefined,
|
||||
adresse: form.adresse,
|
||||
email: form.email || undefined,
|
||||
nennbetrag_eur: form.nennbetrag_eur,
|
||||
is_geschaeftsfuehrer: form.is_geschaeftsfuehrer,
|
||||
internal_role: form.internal_role || undefined,
|
||||
has_academic_background: form.has_academic_background,
|
||||
ip_areas: ip_areas.length > 0 ? ip_areas : undefined,
|
||||
})
|
||||
setForm({ name: '', geburtsdatum: '', adresse: '', email: '', nennbetrag_eur: 12500,
|
||||
is_geschaeftsfuehrer: true, internal_role: '', has_academic_background: false, ip_areas: '' })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<h3 className="font-semibold mb-3">Neuen Gesellschafter hinzufügen</h3>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<input
|
||||
data-testid="gs-name"
|
||||
placeholder="Name"
|
||||
value={form.name}
|
||||
onChange={e => setForm({ ...form, name: e.target.value })}
|
||||
className="px-3 py-2 border rounded"
|
||||
/>
|
||||
<input
|
||||
data-testid="gs-birthdate"
|
||||
type="date"
|
||||
placeholder="Geburtsdatum"
|
||||
value={form.geburtsdatum}
|
||||
onChange={e => setForm({ ...form, geburtsdatum: e.target.value })}
|
||||
className="px-3 py-2 border rounded"
|
||||
/>
|
||||
<input
|
||||
data-testid="gs-address"
|
||||
placeholder="Adresse (Straße, PLZ Ort)"
|
||||
value={form.adresse}
|
||||
onChange={e => setForm({ ...form, adresse: e.target.value })}
|
||||
className="px-3 py-2 border rounded col-span-2"
|
||||
/>
|
||||
<input
|
||||
data-testid="gs-email"
|
||||
type="email"
|
||||
placeholder="E-Mail (optional)"
|
||||
value={form.email}
|
||||
onChange={e => setForm({ ...form, email: e.target.value })}
|
||||
className="px-3 py-2 border rounded"
|
||||
/>
|
||||
<input
|
||||
data-testid="gs-nennbetrag"
|
||||
type="number"
|
||||
min={1}
|
||||
step={1}
|
||||
placeholder="Nennbetrag in EUR"
|
||||
value={form.nennbetrag_eur}
|
||||
onChange={e => setForm({ ...form, nennbetrag_eur: parseInt(e.target.value) || 0 })}
|
||||
className="px-3 py-2 border rounded"
|
||||
/>
|
||||
<select
|
||||
data-testid="gs-role"
|
||||
value={form.internal_role}
|
||||
onChange={e => setForm({ ...form, internal_role: e.target.value })}
|
||||
className="px-3 py-2 border rounded bg-white"
|
||||
>
|
||||
<option value="">Rolle wählen…</option>
|
||||
<option value="CEO">CEO (Chief Executive Officer)</option>
|
||||
<option value="CTO">CTO (Chief Technical Officer)</option>
|
||||
<option value="CFO">CFO (Chief Financial Officer)</option>
|
||||
<option value="COO">COO (Chief Operating Officer)</option>
|
||||
<option value="CPO">CPO (Chief Product Officer)</option>
|
||||
<option value="Geschäftsführer">Geschäftsführer (ohne Spezialisierung)</option>
|
||||
<option value="Gesellschafter">Gesellschafter (kein GF)</option>
|
||||
<option value="Sonstige">Sonstige</option>
|
||||
</select>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
data-testid="gs-is-gf"
|
||||
checked={form.is_geschaeftsfuehrer}
|
||||
onChange={e => setForm({ ...form, is_geschaeftsfuehrer: e.target.checked })}
|
||||
/>
|
||||
<label className="text-sm">Geschäftsführer/in</label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
data-testid="gs-academic"
|
||||
checked={form.has_academic_background}
|
||||
onChange={e => setForm({ ...form, has_academic_background: e.target.checked })}
|
||||
/>
|
||||
<label className="text-sm">Akademischer Hintergrund</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
IP-Bereiche, die diese Person in die Gesellschaft einbringt
|
||||
<span className="text-gray-400"> (optional, eine Zeile pro Bereich)</span>
|
||||
</label>
|
||||
<textarea
|
||||
data-testid="gs-ip-areas"
|
||||
value={form.ip_areas}
|
||||
onChange={e => setForm({ ...form, ip_areas: e.target.value })}
|
||||
rows={3}
|
||||
placeholder={'z.B.\nCompliance-Engine (Quellcode + Architektur)\nRAG-Pipeline\nKonfigurationsdaten'}
|
||||
className="w-full px-3 py-2 border rounded font-mono text-xs"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Bei mehreren Gründern wird pro Person ein eigener IP-Assignment-Vertrag generiert.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
data-testid="add-gesellschafter"
|
||||
onClick={handleAdd}
|
||||
disabled={!form.name.trim() || form.nennbetrag_eur < 1}
|
||||
className="mt-3 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50"
|
||||
>
|
||||
Gesellschafter hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="font-semibold mb-3">Gesellschafter ({state.gesellschafter.length})</h3>
|
||||
{state.gesellschafter.length === 0 ? (
|
||||
<p className="text-gray-500 text-sm">Noch keine Gesellschafter angelegt.</p>
|
||||
) : (
|
||||
<table className="w-full text-sm" data-testid="gs-table">
|
||||
<thead className="bg-gray-100">
|
||||
<tr>
|
||||
<th className="px-3 py-2 text-left">Nr.</th>
|
||||
<th className="px-3 py-2 text-left">Name</th>
|
||||
<th className="px-3 py-2 text-left">Geburtsdatum</th>
|
||||
<th className="px-3 py-2 text-right">Nennbetrag</th>
|
||||
<th className="px-3 py-2 text-right">Anteil %</th>
|
||||
<th className="px-3 py-2">GF?</th>
|
||||
<th className="px-3 py-2"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{state.gesellschafter.map(g => (
|
||||
<tr key={g.id} className="border-t" data-testid={`gs-row-${g.anteil_nr}`}>
|
||||
<td className="px-3 py-2">{g.anteil_nr}</td>
|
||||
<td className="px-3 py-2 font-medium">
|
||||
{g.name}{g.internal_role ? ` (${g.internal_role})` : ''}
|
||||
{g.ip_areas && g.ip_areas.length > 0 && (
|
||||
<div className="text-xs text-gray-500 mt-0.5">
|
||||
IP: {g.ip_areas.join(', ')}
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-3 py-2">{g.geburtsdatum || '—'}</td>
|
||||
<td className="px-3 py-2 text-right">{g.nennbetrag_eur.toLocaleString('de-DE')} €</td>
|
||||
<td className="px-3 py-2 text-right">{((g.nennbetrag_eur / Math.max(target, 1)) * 100).toFixed(2)}%</td>
|
||||
<td className="px-3 py-2 text-center">{g.is_geschaeftsfuehrer ? '✓' : '—'}</td>
|
||||
<td className="px-3 py-2">
|
||||
<button
|
||||
onClick={() => removeGesellschafter(g.id)}
|
||||
className="text-red-600 hover:underline text-xs"
|
||||
>
|
||||
Entfernen
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
<tr className="border-t-2 font-semibold bg-gray-50">
|
||||
<td colSpan={3} className="px-3 py-2">Summe</td>
|
||||
<td className="px-3 py-2 text-right" data-testid="gs-total">
|
||||
{totalNennbetrag.toLocaleString('de-DE')} €
|
||||
</td>
|
||||
<td className="px-3 py-2 text-right">
|
||||
{totalNennbetrag === target ? '100%' : `≠ ${target.toLocaleString('de-DE')} €`}
|
||||
</td>
|
||||
<td colSpan={2}></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
{totalNennbetrag !== target && state.gesellschafter.length > 0 && (
|
||||
<p className="mt-2 text-sm text-orange-600">
|
||||
⚠ Die Summe der Nennbeträge ({totalNennbetrag.toLocaleString('de-DE')} €)
|
||||
entspricht nicht dem Stammkapital ({target.toLocaleString('de-DE')} €).
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* Kombinierte einfache Steps: Geschäftsführer (3), Kapital (4), Notar (5), SHA (6).
|
||||
* Jeder Sub-Step ist eine simple Form.
|
||||
*/
|
||||
|
||||
import type { FoundingWizardState, GFContract } from '@/lib/sdk/founding/types'
|
||||
|
||||
interface PropsBase {
|
||||
state: FoundingWizardState
|
||||
update: <K extends keyof FoundingWizardState>(k: K, v: FoundingWizardState[K]) => void
|
||||
}
|
||||
|
||||
export function StepGFAssignment({ state, update }: PropsBase) {
|
||||
const founders = state.gesellschafter
|
||||
const toggleGF = (id: string, val: boolean) => {
|
||||
update('gesellschafter', state.gesellschafter.map(g => g.id === id ? { ...g, is_geschaeftsfuehrer: val } : g))
|
||||
}
|
||||
const setRole = (id: string, role: string) => {
|
||||
update('gesellschafter', state.gesellschafter.map(g => g.id === id ? { ...g, internal_role: role } : g))
|
||||
}
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-gray-600">
|
||||
Wähle, welche Gesellschafter zu Geschäftsführern bestellt werden sollen. Standardmäßig sind alle Gründer auch GF.
|
||||
</p>
|
||||
{founders.length === 0 ? (
|
||||
<p className="text-orange-600">Bitte zuerst Gesellschafter in Step 2 anlegen.</p>
|
||||
) : (
|
||||
<table className="w-full text-sm" data-testid="gf-assignment-table">
|
||||
<thead className="bg-gray-100">
|
||||
<tr>
|
||||
<th className="px-3 py-2 text-left">Gesellschafter</th>
|
||||
<th className="px-3 py-2 text-left">Interne Rolle (CEO, CTO, ...)</th>
|
||||
<th className="px-3 py-2">GF?</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{founders.map(g => (
|
||||
<tr key={g.id} className="border-t">
|
||||
<td className="px-3 py-2 font-medium">{g.name}</td>
|
||||
<td className="px-3 py-2">
|
||||
<input
|
||||
value={g.internal_role || ''}
|
||||
onChange={e => setRole(g.id, e.target.value)}
|
||||
className="px-2 py-1 border rounded w-48"
|
||||
placeholder="CEO, CTO, COO..."
|
||||
/>
|
||||
</td>
|
||||
<td className="px-3 py-2 text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
data-testid={`gf-toggle-${g.anteil_nr}`}
|
||||
checked={g.is_geschaeftsfuehrer}
|
||||
onChange={e => toggleGF(g.id, e.target.checked)}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function StepCapital({ state, update }: PropsBase) {
|
||||
const c = state.capital
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Stammkapital (EUR)</label>
|
||||
<input
|
||||
data-testid="stammkapital"
|
||||
type="number" min={1} step={1}
|
||||
value={c.stammkapital_eur}
|
||||
onChange={e => update('capital', { ...c, stammkapital_eur: parseInt(e.target.value) || 0 })}
|
||||
className="w-full px-3 py-2 border rounded-lg"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-gray-500">GmbH: mind. 25.000 €, UG: ab 1 €</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Einlage-Art</label>
|
||||
<select
|
||||
data-testid="einlage-method"
|
||||
value={c.einlage_method}
|
||||
onChange={e => update('capital', { ...c, einlage_method: e.target.value as typeof c.einlage_method })}
|
||||
className="w-full px-3 py-2 border rounded-lg"
|
||||
>
|
||||
<option value="Geld">Bargründung</option>
|
||||
<option value="Sacheinlage">Sachgründung</option>
|
||||
<option value="Geld und Sacheinlage">Misch-Gründung</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Sofortige Einzahlung (%)
|
||||
</label>
|
||||
<input
|
||||
data-testid="einlage-quote"
|
||||
type="number" min={25} max={100}
|
||||
value={c.einlage_quote_initial_pct}
|
||||
onChange={e => update('capital', { ...c, einlage_quote_initial_pct: parseInt(e.target.value) || 50 })}
|
||||
className="w-full px-3 py-2 border rounded-lg"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-gray-500">Mind. 25% gem. § 7 Abs. 2 GmbHG, Standard 50%</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mt-7">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="has_sach"
|
||||
data-testid="has-sacheinlage"
|
||||
checked={c.has_sacheinlage}
|
||||
onChange={e => update('capital', { ...c, has_sacheinlage: e.target.checked })}
|
||||
/>
|
||||
<label htmlFor="has_sach" className="text-sm">Sacheinlage-Klausel aktivieren</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function StepNotar({ state, update }: PropsBase) {
|
||||
const n = state.notar
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Name des Notars</label>
|
||||
<input
|
||||
data-testid="notary-name"
|
||||
value={n.notary_name}
|
||||
onChange={e => update('notar', { ...n, notary_name: e.target.value })}
|
||||
placeholder="z.B. Dr. Müller"
|
||||
className="w-full px-3 py-2 border rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Notarsitz</label>
|
||||
<input
|
||||
data-testid="notary-place"
|
||||
value={n.notary_place}
|
||||
onChange={e => update('notar', { ...n, notary_place: e.target.value })}
|
||||
placeholder="z.B. Stuttgart"
|
||||
className="w-full px-3 py-2 border rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Adresse</label>
|
||||
<input
|
||||
data-testid="notary-address"
|
||||
value={n.notary_address || ''}
|
||||
onChange={e => update('notar', { ...n, notary_address: e.target.value })}
|
||||
className="w-full px-3 py-2 border rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Geplanter Notartermin</label>
|
||||
<input
|
||||
data-testid="notarial-date"
|
||||
type="date"
|
||||
value={n.notarial_date || ''}
|
||||
onChange={e => update('notar', { ...n, notarial_date: e.target.value })}
|
||||
className="w-full px-3 py-2 border rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3 text-sm text-blue-900">
|
||||
<strong>Hinweis:</strong> Die URNr. wird vom Notar beim Beurkundungstermin vergeben. Du kannst die generierte
|
||||
HRB-Anmeldung als Vorbereitungsdokument zum Termin mitnehmen.
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function StepSHAConfig({ state, update }: PropsBase) {
|
||||
const s = state.sha
|
||||
const updateField = <K extends keyof typeof s>(k: K, v: typeof s[K]) => update('sha', { ...s, [k]: v })
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
data-testid="has-sha"
|
||||
checked={s.has_sha}
|
||||
onChange={e => updateField('has_sha', e.target.checked)}
|
||||
/>
|
||||
<label className="text-sm font-medium">SHA (Shareholders' Agreement) ist Teil des Notartermin-Pakets</label>
|
||||
</div>
|
||||
|
||||
{s.has_sha && (
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm text-gray-700 mb-1">Vesting-Dauer (Monate)</label>
|
||||
<input data-testid="vesting-months" type="number" value={s.vesting_months}
|
||||
onChange={e => updateField('vesting_months', parseInt(e.target.value) || 48)}
|
||||
className="w-full px-3 py-2 border rounded-lg" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm text-gray-700 mb-1">Cliff (Monate)</label>
|
||||
<input data-testid="cliff-months" type="number" value={s.cliff_months}
|
||||
onChange={e => updateField('cliff_months', parseInt(e.target.value) || 12)}
|
||||
className="w-full px-3 py-2 border rounded-lg" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm text-gray-700 mb-1">Drag-Along Schwelle (%)</label>
|
||||
<input data-testid="drag-along-pct" type="number" value={s.drag_along_threshold_pct}
|
||||
onChange={e => updateField('drag_along_threshold_pct', parseInt(e.target.value) || 75)}
|
||||
className="w-full px-3 py-2 border rounded-lg" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm text-gray-700 mb-1">Reserved-Matters Mehrheit (%)</label>
|
||||
<input data-testid="reserved-matters-pct" type="number" value={s.reserved_matters_majority_pct}
|
||||
onChange={e => updateField('reserved_matters_majority_pct', parseInt(e.target.value) || 75)}
|
||||
className="w-full px-3 py-2 border rounded-lg" />
|
||||
</div>
|
||||
<div className="col-span-2 grid grid-cols-3 gap-3 mt-2">
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<input type="checkbox" data-testid="has-beirat" checked={s.has_beirat}
|
||||
onChange={e => updateField('has_beirat', e.target.checked)} />
|
||||
Beirat einrichten
|
||||
</label>
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<input type="checkbox" data-testid="has-texas" checked={s.has_texas_shootout}
|
||||
onChange={e => updateField('has_texas_shootout', e.target.checked)} />
|
||||
Texas Shoot-Out (Deadlock)
|
||||
</label>
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<input type="checkbox" data-testid="has-ceo" checked={s.has_ceo_designation}
|
||||
onChange={e => updateField('has_ceo_designation', e.target.checked)} />
|
||||
CEO mit Stichentscheid
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface GFContractStepProps extends PropsBase {
|
||||
gf_list: Array<{ id: string; name: string; internal_role?: string }>
|
||||
upsertGFContract: (c: GFContract) => void
|
||||
}
|
||||
|
||||
export function StepGFContracts({ state, gf_list, upsertGFContract }: GFContractStepProps) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-gray-600">
|
||||
Für jeden Geschäftsführer wird ein Dienstvertrag generiert. Bitte Eckdaten ausfüllen.
|
||||
</p>
|
||||
{gf_list.length === 0 ? (
|
||||
<p className="text-orange-600">Bitte zuerst in Step 2 mindestens einen GF anlegen.</p>
|
||||
) : (
|
||||
gf_list.map(gf => {
|
||||
const c = state.gf_contracts.find(x => x.gesellschafter_id === gf.id) || {
|
||||
gesellschafter_id: gf.id,
|
||||
gross_annual_salary_eur: 84000,
|
||||
has_bonus: false,
|
||||
has_company_car: false,
|
||||
has_bav: false,
|
||||
vacation_days: 30,
|
||||
kuendigungsfrist_gesellschaft_monate: 6,
|
||||
kuendigungsfrist_gf_monate: 3,
|
||||
para_181_release: true,
|
||||
sv_status: 'sozialversicherungsfrei' as const,
|
||||
}
|
||||
const u = (patch: Partial<GFContract>) => upsertGFContract({ ...c, ...patch })
|
||||
return (
|
||||
<div key={gf.id} className="border rounded-lg p-4" data-testid={`contract-${gf.id}`}>
|
||||
<h4 className="font-semibold mb-3">{gf.name} {gf.internal_role && `(${gf.internal_role})`}</h4>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label className="block text-xs text-gray-700 mb-1">Jahresgehalt (EUR brutto)</label>
|
||||
<input
|
||||
data-testid={`salary-${gf.id}`}
|
||||
type="number"
|
||||
value={c.gross_annual_salary_eur}
|
||||
onChange={e => u({ gross_annual_salary_eur: parseInt(e.target.value) || 0 })}
|
||||
className="w-full px-2 py-1 border rounded"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-gray-700 mb-1">Urlaubstage</label>
|
||||
<input type="number" value={c.vacation_days}
|
||||
onChange={e => u({ vacation_days: parseInt(e.target.value) || 30 })}
|
||||
className="w-full px-2 py-1 border rounded" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-gray-700 mb-1">SV-Status</label>
|
||||
<select value={c.sv_status} onChange={e => u({ sv_status: e.target.value as GFContract['sv_status'] })}
|
||||
className="w-full px-2 py-1 border rounded">
|
||||
<option value="sozialversicherungsfrei">sv-frei (Standard für GF/Gesellschafter)</option>
|
||||
<option value="sozialversicherungspflichtig">sv-pflichtig</option>
|
||||
<option value="noch zu klären">noch zu klären</option>
|
||||
</select>
|
||||
</div>
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<input type="checkbox" checked={c.para_181_release}
|
||||
onChange={e => u({ para_181_release: e.target.checked })} />
|
||||
§ 181 BGB-Befreiung
|
||||
</label>
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<input type="checkbox" checked={c.has_bonus}
|
||||
onChange={e => u({ has_bonus: e.target.checked })} />
|
||||
Bonus-Vereinbarung
|
||||
</label>
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<input type="checkbox" checked={c.has_company_car}
|
||||
onChange={e => u({ has_company_car: e.target.checked })} />
|
||||
Firmenfahrzeug
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
defaultFoundingWizardState,
|
||||
type FoundingWizardState,
|
||||
type Gesellschafter,
|
||||
type GFContract,
|
||||
type GeneratedDocument,
|
||||
} from '@/lib/sdk/founding/types'
|
||||
|
||||
const STORAGE_KEY = 'breakpilot:founding-wizard:state:v1'
|
||||
|
||||
export const FOUNDING_WIZARD_STEPS = [
|
||||
{ id: 1, name: 'Stage & Basics', description: 'Unternehmensname, Sitz, Gegenstand' },
|
||||
{ id: 2, name: 'Gesellschafter', description: 'Gründer und ihre Anteile' },
|
||||
{ id: 3, name: 'Geschäftsführer', description: 'GF-Bestellung und Rollen' },
|
||||
{ id: 4, name: 'Kapital', description: 'Stammkapital und Einzahlung' },
|
||||
{ id: 5, name: 'Notar', description: 'Notartermin und Beurkundung' },
|
||||
{ id: 6, name: 'SHA-Optionen', description: 'Vesting, Drag-Along, Reserved Matters' },
|
||||
{ id: 7, name: 'GF-Verträge', description: 'Vergütung, D&O, Kündigungsfristen' },
|
||||
{ id: 8, name: 'Dokumente generieren', description: 'Auswahl und Word-Export' },
|
||||
]
|
||||
|
||||
export function useFoundingWizardForm() {
|
||||
const [state, setState] = useState<FoundingWizardState>(defaultFoundingWizardState())
|
||||
const [hydrated, setHydrated] = useState(false)
|
||||
const [generating, setGenerating] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
// Hydrate from localStorage
|
||||
useEffect(() => {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY)
|
||||
if (raw) {
|
||||
const parsed = JSON.parse(raw)
|
||||
setState({ ...defaultFoundingWizardState(), ...parsed })
|
||||
}
|
||||
} catch {
|
||||
// ignore corrupted storage
|
||||
}
|
||||
setHydrated(true)
|
||||
}, [])
|
||||
|
||||
// Persist on every change after hydration
|
||||
useEffect(() => {
|
||||
if (!hydrated) return
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(state))
|
||||
} catch {
|
||||
// quota exceeded - ignore
|
||||
}
|
||||
}, [state, hydrated])
|
||||
|
||||
const update = useCallback(<K extends keyof FoundingWizardState>(
|
||||
key: K,
|
||||
value: FoundingWizardState[K] | ((prev: FoundingWizardState[K]) => FoundingWizardState[K])
|
||||
) => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
[key]: typeof value === 'function' ? (value as Function)(prev[key]) : value,
|
||||
}))
|
||||
}, [])
|
||||
|
||||
const setStep = useCallback((step: number) => {
|
||||
setState(prev => ({ ...prev, current_step: step }))
|
||||
}, [])
|
||||
|
||||
const nextStep = useCallback(() => {
|
||||
setState(prev => ({ ...prev, current_step: Math.min(prev.current_step + 1, FOUNDING_WIZARD_STEPS.length) }))
|
||||
}, [])
|
||||
|
||||
const prevStep = useCallback(() => {
|
||||
setState(prev => ({ ...prev, current_step: Math.max(prev.current_step - 1, 1) }))
|
||||
}, [])
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setState(defaultFoundingWizardState())
|
||||
try { localStorage.removeItem(STORAGE_KEY) } catch {}
|
||||
}, [])
|
||||
|
||||
// Gesellschafter helpers
|
||||
const addGesellschafter = useCallback((gs: Omit<Gesellschafter, 'id' | 'anteil_nr'>) => {
|
||||
setState(prev => {
|
||||
const nextNr = (prev.gesellschafter.reduce((m, g) => Math.max(m, g.anteil_nr), 0)) + 1
|
||||
const id = `gs_${Date.now()}_${nextNr}`
|
||||
return { ...prev, gesellschafter: [...prev.gesellschafter, { ...gs, id, anteil_nr: nextNr }] }
|
||||
})
|
||||
}, [])
|
||||
|
||||
const updateGesellschafter = useCallback((id: string, patch: Partial<Gesellschafter>) => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
gesellschafter: prev.gesellschafter.map(g => g.id === id ? { ...g, ...patch } : g),
|
||||
}))
|
||||
}, [])
|
||||
|
||||
const removeGesellschafter = useCallback((id: string) => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
gesellschafter: prev.gesellschafter.filter(g => g.id !== id),
|
||||
gf_contracts: prev.gf_contracts.filter(c => c.gesellschafter_id !== id),
|
||||
}))
|
||||
}, [])
|
||||
|
||||
// GF Contract helpers
|
||||
const upsertGFContract = useCallback((contract: GFContract) => {
|
||||
setState(prev => {
|
||||
const idx = prev.gf_contracts.findIndex(c => c.gesellschafter_id === contract.gesellschafter_id)
|
||||
const next = [...prev.gf_contracts]
|
||||
if (idx >= 0) next[idx] = contract
|
||||
else next.push(contract)
|
||||
return { ...prev, gf_contracts: next }
|
||||
})
|
||||
}, [])
|
||||
|
||||
// Validation (canProceed for current step)
|
||||
const canProceed = useMemo(() => {
|
||||
switch (state.current_step) {
|
||||
case 1:
|
||||
return state.basics.company_name.trim().length > 1 &&
|
||||
state.basics.company_seat.trim().length > 1 &&
|
||||
state.basics.company_purpose_description.trim().length > 10
|
||||
case 2: {
|
||||
if (state.gesellschafter.length < 1) return false
|
||||
const sum = state.gesellschafter.reduce((s, g) => s + (g.nennbetrag_eur || 0), 0)
|
||||
return sum === state.capital.stammkapital_eur
|
||||
}
|
||||
case 3:
|
||||
return state.gesellschafter.some(g => g.is_geschaeftsfuehrer)
|
||||
case 4:
|
||||
return state.capital.stammkapital_eur >= 25000
|
||||
case 5:
|
||||
return state.notar.notary_name.trim().length > 1 && state.notar.notary_place.trim().length > 1
|
||||
case 6:
|
||||
return true
|
||||
case 7:
|
||||
return state.gesellschafter.filter(g => g.is_geschaeftsfuehrer)
|
||||
.every(g => state.gf_contracts.some(c => c.gesellschafter_id === g.id))
|
||||
case 8:
|
||||
return state.selected_documents.length > 0
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}, [state])
|
||||
|
||||
const generateDocuments = useCallback(async (): Promise<GeneratedDocument[]> => {
|
||||
setGenerating(true)
|
||||
setError(null)
|
||||
try {
|
||||
const response = await fetch('/api/v1/founding-wizard/generate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(state),
|
||||
})
|
||||
if (!response.ok) {
|
||||
throw new Error(`Generierung fehlgeschlagen: ${response.status}`)
|
||||
}
|
||||
const data = await response.json()
|
||||
const docs: GeneratedDocument[] = data.documents || []
|
||||
setState(prev => ({ ...prev, generated_documents: docs }))
|
||||
return docs
|
||||
} catch (e: unknown) {
|
||||
const msg = e instanceof Error ? e.message : 'Unbekannter Fehler'
|
||||
setError(msg)
|
||||
throw e
|
||||
} finally {
|
||||
setGenerating(false)
|
||||
}
|
||||
}, [state])
|
||||
|
||||
// Derived: hat zugehöriger GF einen Vertrag?
|
||||
const gf_list = useMemo(
|
||||
() => state.gesellschafter.filter(g => g.is_geschaeftsfuehrer),
|
||||
[state.gesellschafter]
|
||||
)
|
||||
|
||||
return {
|
||||
state, hydrated, generating, error,
|
||||
update, setStep, nextStep, prevStep, reset,
|
||||
addGesellschafter, updateGesellschafter, removeGesellschafter,
|
||||
upsertGFContract,
|
||||
canProceed, generateDocuments,
|
||||
gf_list,
|
||||
steps: FOUNDING_WIZARD_STEPS,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import { useFoundingWizardForm } from './_hooks/useFoundingWizardForm'
|
||||
import { StepBasics } from './_components/StepBasics'
|
||||
import { StepGesellschafter } from './_components/StepGesellschafter'
|
||||
import { StepCapital, StepGFAssignment, StepGFContracts, StepNotar, StepSHAConfig } from './_components/StepsSimpleConfig'
|
||||
import { StepGenerate } from './_components/StepGenerate'
|
||||
|
||||
export default function FoundingWizardPage() {
|
||||
const {
|
||||
state, hydrated, generating, error,
|
||||
update, nextStep, prevStep, reset,
|
||||
addGesellschafter, updateGesellschafter, removeGesellschafter,
|
||||
upsertGFContract,
|
||||
canProceed, generateDocuments,
|
||||
gf_list, steps,
|
||||
} = useFoundingWizardForm()
|
||||
|
||||
if (!hydrated) return null
|
||||
|
||||
const isLastStep = state.current_step === steps.length
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-8" data-testid="founding-wizard">
|
||||
<div className="max-w-5xl mx-auto px-4">
|
||||
{/* Header */}
|
||||
<div className="mb-8 flex justify-between items-start">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">Gründungs-Wizard</h1>
|
||||
<p className="text-gray-600 mt-2">
|
||||
Erstellt alle Notartermin-Dokumente für Deine GmbH/UG-Gründung in 8 Schritten.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
data-testid="reset-wizard"
|
||||
onClick={() => { if (confirm('Wizard-Daten zurücksetzen?')) reset() }}
|
||||
className="text-sm text-gray-500 hover:text-red-600"
|
||||
>
|
||||
Zurücksetzen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Progress Steps */}
|
||||
<div className="mb-8" data-testid="wizard-progress">
|
||||
<div className="flex items-center justify-between">
|
||||
{steps.map((step, idx) => (
|
||||
<React.Fragment key={step.id}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => state.current_step > step.id && update('current_step', step.id)}
|
||||
className="flex items-center"
|
||||
data-testid={`step-indicator-${step.id}`}
|
||||
>
|
||||
<div className={`w-9 h-9 rounded-full flex items-center justify-center text-sm font-medium ${
|
||||
step.id < state.current_step ? 'bg-purple-600 text-white' :
|
||||
step.id === state.current_step ? 'bg-purple-100 text-purple-600 border-2 border-purple-600' :
|
||||
'bg-gray-100 text-gray-400'
|
||||
}`}>
|
||||
{step.id < state.current_step ? '✓' : step.id}
|
||||
</div>
|
||||
<div className="ml-2 hidden md:block text-left">
|
||||
<div className={`text-xs font-medium ${step.id <= state.current_step ? 'text-gray-900' : 'text-gray-400'}`}>
|
||||
{step.name}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
{idx < steps.length - 1 && (
|
||||
<div className={`flex-1 h-0.5 mx-2 ${step.id < state.current_step ? 'bg-purple-600' : 'bg-gray-200'}`} />
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step Content */}
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-8">
|
||||
<div className="mb-6">
|
||||
<h2 className="text-xl font-semibold text-gray-900">
|
||||
{steps[state.current_step - 1]?.name}
|
||||
</h2>
|
||||
<p className="text-gray-500 text-sm">{steps[state.current_step - 1]?.description}</p>
|
||||
</div>
|
||||
|
||||
<div data-testid={`step-content-${state.current_step}`}>
|
||||
{state.current_step === 1 && <StepBasics state={state} update={update} />}
|
||||
{state.current_step === 2 && (
|
||||
<StepGesellschafter
|
||||
state={state}
|
||||
addGesellschafter={addGesellschafter}
|
||||
updateGesellschafter={updateGesellschafter}
|
||||
removeGesellschafter={removeGesellschafter}
|
||||
/>
|
||||
)}
|
||||
{state.current_step === 3 && <StepGFAssignment state={state} update={update} />}
|
||||
{state.current_step === 4 && <StepCapital state={state} update={update} />}
|
||||
{state.current_step === 5 && <StepNotar state={state} update={update} />}
|
||||
{state.current_step === 6 && <StepSHAConfig state={state} update={update} />}
|
||||
{state.current_step === 7 && (
|
||||
<StepGFContracts state={state} update={update} gf_list={gf_list} upsertGFContract={upsertGFContract} />
|
||||
)}
|
||||
{state.current_step === 8 && (
|
||||
<StepGenerate
|
||||
state={state}
|
||||
update={update}
|
||||
generating={generating}
|
||||
error={error}
|
||||
onGenerate={generateDocuments}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
{!isLastStep && (
|
||||
<div className="flex justify-between items-center mt-8 pt-6 border-t border-gray-200">
|
||||
<button
|
||||
data-testid="prev-step"
|
||||
onClick={prevStep}
|
||||
disabled={state.current_step === 1}
|
||||
className="px-6 py-3 text-gray-600 hover:text-gray-900 disabled:opacity-50"
|
||||
>
|
||||
Zurück
|
||||
</button>
|
||||
<span className="text-xs text-gray-400">
|
||||
Schritt {state.current_step} von {steps.length}
|
||||
</span>
|
||||
<button
|
||||
data-testid="next-step"
|
||||
onClick={nextStep}
|
||||
disabled={!canProceed}
|
||||
className="px-8 py-3 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50"
|
||||
>
|
||||
Weiter
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -39,11 +39,19 @@ export function HazardTable({ hazards, lifecyclePhases, onDelete }: {
|
||||
.map((hazard) => (
|
||||
<tr key={hazard.id} className="hover:bg-gray-50 dark:hover:bg-gray-750 transition-colors">
|
||||
<td className="px-4 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<div className="text-sm font-medium text-gray-900 dark:text-white">{hazard.name}</div>
|
||||
{hazard.name.startsWith('Auto:') && (
|
||||
<span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-green-100 text-green-700">Auto</span>
|
||||
)}
|
||||
{(hazard as { pattern_id?: string }).pattern_id && (
|
||||
<span
|
||||
className="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-mono font-medium bg-slate-100 text-slate-700 border border-slate-200 cursor-help"
|
||||
title={`Quelle: BreakPilot IACE Pattern-Engine (${(hazard as { pattern_id?: string }).pattern_id}). Lizenzregel R3 — Eigenwerk, kein externer Lizenz-Footer noetig. Pattern-Definition mit Norm-Referenzen siehe Library.`}
|
||||
>
|
||||
{(hazard as { pattern_id?: string }).pattern_id} · R3
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{hazard.description && (
|
||||
<div className="text-xs text-gray-500 truncate max-w-[250px]">{hazard.description}</div>
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
'use client'
|
||||
|
||||
// LLM Gap-Review Modal — Task #8.
|
||||
//
|
||||
// Triggers POST /projects/:id/llm-gap-review on mount and lists the
|
||||
// LLM's gap suggestions with an Adopt / Reject UX. Adoption goes through
|
||||
// the regular CreateHazard / CreateMitigation endpoints — the modal
|
||||
// itself never mutates project state on its own.
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
type Suggestion = {
|
||||
kind: 'hazard' | 'mitigation'
|
||||
title: string
|
||||
description: string
|
||||
category?: string
|
||||
hazard_ref?: string
|
||||
pattern_ref?: string
|
||||
norm_refs?: string[]
|
||||
confidence?: 'high' | 'medium' | 'low'
|
||||
rationale?: string
|
||||
}
|
||||
|
||||
type Response = {
|
||||
project_id: string
|
||||
source: 'llm_gap_review' | 'fallback_static'
|
||||
model?: string
|
||||
suggestions: Suggestion[]
|
||||
input_summary: {
|
||||
hazard_count: number
|
||||
mitigation_count: number
|
||||
limits_form_fields: number
|
||||
}
|
||||
}
|
||||
|
||||
const CONF_COLOR: Record<string, string> = {
|
||||
high: 'bg-emerald-100 text-emerald-800 border-emerald-200',
|
||||
medium: 'bg-amber-100 text-amber-800 border-amber-200',
|
||||
low: 'bg-slate-100 text-slate-600 border-slate-200',
|
||||
}
|
||||
|
||||
interface Props {
|
||||
projectId: string
|
||||
onClose: () => void
|
||||
onAdoptHazard?: (s: Suggestion) => Promise<void>
|
||||
onAdoptMitigation?: (s: Suggestion) => Promise<void>
|
||||
}
|
||||
|
||||
export function LLMGapReviewModal({ projectId, onClose, onAdoptHazard, onAdoptMitigation }: Props) {
|
||||
const [data, setData] = useState<Response | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [adopted, setAdopted] = useState<Set<number>>(new Set())
|
||||
const [rejected, setRejected] = useState<Set<number>>(new Set())
|
||||
const [adopting, setAdopting] = useState<number | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
fetch(`/api/sdk/v1/iace/projects/${projectId}/llm-gap-review`, { method: 'POST' })
|
||||
.then((r) => (r.ok ? r.json() : Promise.reject(`HTTP ${r.status}`)))
|
||||
.then(setData)
|
||||
.catch((e) => setError(String(e)))
|
||||
.finally(() => setLoading(false))
|
||||
}, [projectId])
|
||||
|
||||
async function adopt(idx: number) {
|
||||
if (!data) return
|
||||
const s = data.suggestions[idx]
|
||||
setAdopting(idx)
|
||||
try {
|
||||
if (s.kind === 'hazard' && onAdoptHazard) await onAdoptHazard(s)
|
||||
else if (s.kind === 'mitigation' && onAdoptMitigation) await onAdoptMitigation(s)
|
||||
setAdopted((prev) => new Set(prev).add(idx))
|
||||
} catch (e) {
|
||||
setError(`Adopt fehlgeschlagen: ${e}`)
|
||||
} finally {
|
||||
setAdopting(null)
|
||||
}
|
||||
}
|
||||
|
||||
function reject(idx: number) {
|
||||
setRejected((prev) => new Set(prev).add(idx))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||
<div className="bg-white rounded-xl shadow-2xl w-full max-w-3xl max-h-[90vh] overflow-hidden flex flex-col">
|
||||
<div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between flex-shrink-0">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-gray-900">KI-Gap-Review</h2>
|
||||
<p className="text-xs text-gray-500 mt-0.5">
|
||||
LLM-gestuetzte Suche nach fehlenden Gefaehrdungen und Schutzmassnahmen — Vorschlaege sind unverbindlich bis explizit uebernommen.
|
||||
</p>
|
||||
</div>
|
||||
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 text-2xl leading-none">×</button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto p-6 space-y-3">
|
||||
{loading && (
|
||||
<div className="text-center py-12">
|
||||
<div className="animate-spin rounded-full h-10 w-10 border-b-2 border-purple-600 mx-auto" />
|
||||
<p className="text-sm text-gray-500 mt-3">LLM laeuft (Qwen/Claude). Das kann bis zu 30 Sekunden dauern.</p>
|
||||
</div>
|
||||
)}
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4 text-sm text-red-700">
|
||||
Fehler: {error}
|
||||
</div>
|
||||
)}
|
||||
{data && (
|
||||
<>
|
||||
<div className="text-xs text-gray-500 flex items-center gap-3 border-b border-gray-100 pb-2">
|
||||
<span>
|
||||
Eingabe: {data.input_summary.hazard_count} Gefaehrdungen,{' '}
|
||||
{data.input_summary.mitigation_count} Massnahmen, {data.input_summary.limits_form_fields} Grenzen-Felder
|
||||
</span>
|
||||
<span className="text-gray-300">·</span>
|
||||
<span>
|
||||
Quelle: {data.source === 'llm_gap_review'
|
||||
? `LLM (${data.model ?? 'unbekannt'})`
|
||||
: 'Statische Fallback-Liste'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{data.suggestions.length === 0 && (
|
||||
<div className="text-center text-gray-500 py-12 text-sm">
|
||||
Keine Lueckenvorschlaege. Die deterministische Pattern-Engine hat vermutlich bereits alle Standard-Gefaehrdungen abgedeckt.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data.suggestions.map((s, i) => {
|
||||
const isAdopted = adopted.has(i)
|
||||
const isRejected = rejected.has(i)
|
||||
const isWorking = adopting === i
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className={`border rounded-lg p-3 ${
|
||||
isAdopted ? 'border-emerald-200 bg-emerald-50' :
|
||||
isRejected ? 'border-slate-200 bg-slate-50 opacity-50' :
|
||||
'border-gray-200 bg-white'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 flex-wrap mb-1">
|
||||
<span className={`px-1.5 py-0.5 text-[10px] rounded font-medium ${
|
||||
s.kind === 'hazard' ? 'bg-red-100 text-red-700' : 'bg-blue-100 text-blue-700'
|
||||
}`}>
|
||||
{s.kind === 'hazard' ? 'Gefaehrdung' : 'Massnahme'}
|
||||
</span>
|
||||
{s.category && (
|
||||
<span className="px-1.5 py-0.5 text-[10px] rounded bg-gray-100 text-gray-700">{s.category}</span>
|
||||
)}
|
||||
{s.confidence && (
|
||||
<span className={`px-1.5 py-0.5 text-[10px] rounded border ${CONF_COLOR[s.confidence]}`}>
|
||||
{s.confidence}
|
||||
</span>
|
||||
)}
|
||||
{(s.norm_refs ?? []).map((n) => (
|
||||
<span key={n} className="px-1.5 py-0.5 text-[10px] rounded bg-indigo-50 text-indigo-700 font-mono">{n}</span>
|
||||
))}
|
||||
{s.pattern_ref && (
|
||||
<span className="px-1.5 py-0.5 text-[10px] rounded bg-purple-50 text-purple-700 font-mono">{s.pattern_ref}</span>
|
||||
)}
|
||||
</div>
|
||||
<h3 className="text-sm font-semibold text-gray-900">{s.title}</h3>
|
||||
<p className="text-xs text-gray-600 mt-1">{s.description}</p>
|
||||
{s.hazard_ref && (
|
||||
<p className="text-[11px] text-gray-500 mt-1">Bezogen auf: <em>{s.hazard_ref}</em></p>
|
||||
)}
|
||||
{s.rationale && (
|
||||
<p className="text-[11px] text-gray-400 mt-1 italic">{s.rationale}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 flex-shrink-0">
|
||||
{!isAdopted && !isRejected && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => adopt(i)}
|
||||
disabled={isWorking}
|
||||
className="px-3 py-1 text-xs bg-emerald-600 text-white rounded hover:bg-emerald-700 disabled:opacity-50"
|
||||
>
|
||||
{isWorking ? '…' : 'Uebernehmen'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => reject(i)}
|
||||
className="px-3 py-1 text-xs text-gray-600 border border-gray-300 rounded hover:bg-gray-50"
|
||||
>
|
||||
Verwerfen
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{isAdopted && <span className="text-xs text-emerald-700 font-medium">✓ Uebernommen</span>}
|
||||
{isRejected && <span className="text-xs text-gray-500">Verworfen</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="px-6 py-3 border-t border-gray-200 bg-gray-50 flex items-center justify-between flex-shrink-0">
|
||||
<p className="text-[11px] text-gray-500">
|
||||
Hinweis: LLM-Vorschlaege sind NICHT die deterministische Engine-Output. Jede Uebernahme wird als <code>source=llm_gap_review</code> markiert.
|
||||
</p>
|
||||
<button onClick={onClose} className="px-3 py-1.5 text-sm border border-gray-300 rounded hover:bg-white">
|
||||
Schliessen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LLMGapReviewModal
|
||||
@@ -12,6 +12,7 @@ import type { ResidualFilter } from './_components/ResidualRiskPanel'
|
||||
import { LibraryModal } from './_components/LibraryModal'
|
||||
import { AutoSuggestPanel } from './_components/AutoSuggestPanel'
|
||||
import { CustomHazardModal } from './_components/CustomHazardModal'
|
||||
import { LLMGapReviewModal } from './_components/LLMGapReviewModal'
|
||||
import { useHazards } from './_hooks/useHazards'
|
||||
|
||||
type ViewMode = 'list' | 'risk' | 'blocks'
|
||||
@@ -22,6 +23,7 @@ export default function HazardsPage() {
|
||||
const h = useHazards(projectId)
|
||||
const [view, setView] = useState<ViewMode>('risk')
|
||||
const [showCustomModal, setShowCustomModal] = useState(false)
|
||||
const [showGapReview, setShowGapReview] = useState(false)
|
||||
const [residualFilter, setResidualFilter] = useState<ResidualFilter>('all')
|
||||
const [decisions, setDecisions] = useState<Record<string, boolean | null>>({})
|
||||
|
||||
@@ -104,6 +106,15 @@ export default function HazardsPage() {
|
||||
</svg>
|
||||
Eigene Gefaehrdung
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowGapReview(true)}
|
||||
title="LLM (Qwen/Claude) prueft auf fehlende Gefaehrdungen und Massnahmen — Vorschlaege sind unverbindlich."
|
||||
className="flex items-center gap-2 px-3 py-2 border border-indigo-300 text-indigo-700 rounded-lg hover:bg-indigo-50 transition-colors text-sm">
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
|
||||
</svg>
|
||||
KI-Gap-Review
|
||||
</button>
|
||||
<button onClick={() => h.setShowForm(true)}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors text-sm">
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
@@ -170,6 +181,13 @@ export default function HazardsPage() {
|
||||
onClose={() => setShowCustomModal(false)} />
|
||||
)}
|
||||
|
||||
{showGapReview && (
|
||||
<LLMGapReviewModal
|
||||
projectId={projectId}
|
||||
onClose={() => setShowGapReview(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{h.hazards.length > 0 ? (
|
||||
view === 'risk' ? (
|
||||
<>
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState } from 'react'
|
||||
|
||||
interface NormMapping {
|
||||
region: string
|
||||
identifier: string
|
||||
relation: string
|
||||
confidence: string
|
||||
notes?: string
|
||||
source_url?: string
|
||||
}
|
||||
|
||||
interface CrossRefResponse {
|
||||
norm_id: string
|
||||
mappings: NormMapping[]
|
||||
notes?: string
|
||||
batch_id?: string
|
||||
}
|
||||
|
||||
const RELATION_COLORS: Record<string, string> = {
|
||||
identical: 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300',
|
||||
equivalent: 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300',
|
||||
partial: 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-300',
|
||||
supersedes: 'bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300',
|
||||
superseded_by: 'bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-400',
|
||||
}
|
||||
|
||||
const CONFIDENCE_COLORS: Record<string, string> = {
|
||||
verified: 'text-emerald-700 dark:text-emerald-300 font-semibold',
|
||||
high: 'text-blue-700 dark:text-blue-300',
|
||||
medium: 'text-amber-700 dark:text-amber-300',
|
||||
low: 'text-red-700 dark:text-red-300',
|
||||
}
|
||||
|
||||
const REGION_LABELS: Record<string, string> = {
|
||||
'EU-DIN': 'EU (DIN)',
|
||||
'INTL-ISO': 'International (ISO/IEC)',
|
||||
'US-ANSI': 'US — ANSI',
|
||||
'US-NFPA': 'US — NFPA',
|
||||
'US-UL': 'US — UL',
|
||||
'US-OSHA': 'US — OSHA',
|
||||
'US-ASME': 'US — ASME',
|
||||
'US-ASTM': 'US — ASTM',
|
||||
'US-SAE': 'US — SAE',
|
||||
'US-NIOSH': 'US — NIOSH',
|
||||
'US-FDA': 'US — FDA',
|
||||
'US-EPA': 'US — EPA',
|
||||
'US-NEMA': 'US — NEMA',
|
||||
'US-NSF': 'US — NSF',
|
||||
'US-API': 'US — API',
|
||||
'US-CPSC': 'US — CPSC',
|
||||
'US-AHRI': 'US — AHRI',
|
||||
'US-ASHRAE': 'US — ASHRAE',
|
||||
'US-FCC': 'US — FCC',
|
||||
'US-DOT': 'US — DOT',
|
||||
'CN-GB': 'China (GB)',
|
||||
'JP-JIS': 'Japan (JIS)',
|
||||
}
|
||||
|
||||
function formatRegion(region: string): string {
|
||||
return REGION_LABELS[region] || region
|
||||
}
|
||||
|
||||
interface Props {
|
||||
normId: string
|
||||
}
|
||||
|
||||
export default function NormCrossRefPanel({ normId }: Props) {
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [data, setData] = useState<CrossRefResponse | null>(null)
|
||||
|
||||
const handleLoad = async () => {
|
||||
if (loaded || loading) return
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const res = await fetch(`/api/sdk/v1/iace/norms-library/${encodeURIComponent(normId)}/crossref`)
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
const json = (await res.json()) as CrossRefResponse
|
||||
setData(json)
|
||||
setLoaded(true)
|
||||
} catch (e: any) {
|
||||
setError(e?.message || 'Fehler beim Laden')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (!loaded && !loading && !error) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleLoad}
|
||||
className="text-xs text-purple-600 hover:text-purple-800 dark:text-purple-400 dark:hover:text-purple-200 font-medium underline-offset-2 hover:underline"
|
||||
>
|
||||
Internationale Aequivalenzen anzeigen (DIN/ANSI/GB/JIS)
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return <div className="text-xs text-gray-500 dark:text-gray-400">Cross-Reference wird geladen…</div>
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="text-xs text-red-600 dark:text-red-400">
|
||||
Cross-Reference konnte nicht geladen werden: {error}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!data || data.mappings.length === 0) {
|
||||
return (
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400 italic">
|
||||
Fuer diese Norm liegt (noch) kein internationales Mapping in der Bibliothek vor.
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-2 mt-2 border-t border-gray-200 dark:border-gray-700 pt-2">
|
||||
<div className="text-xs font-medium text-gray-700 dark:text-gray-300">
|
||||
Internationale Aequivalenzen
|
||||
</div>
|
||||
{data.notes && (
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400 italic">{data.notes}</div>
|
||||
)}
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-xs">
|
||||
<thead>
|
||||
<tr className="text-gray-500 dark:text-gray-400 border-b border-gray-200 dark:border-gray-700">
|
||||
<th className="text-left py-1 pr-3 font-medium">Region</th>
|
||||
<th className="text-left py-1 pr-3 font-medium">Identifier</th>
|
||||
<th className="text-left py-1 pr-3 font-medium">Relation</th>
|
||||
<th className="text-left py-1 pr-3 font-medium">Confidence</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.mappings.map((m, i) => (
|
||||
<tr key={i} className="border-b border-gray-100 dark:border-gray-800 last:border-0 align-top">
|
||||
<td className="py-1 pr-3 text-gray-600 dark:text-gray-400 whitespace-nowrap">{formatRegion(m.region)}</td>
|
||||
<td className="py-1 pr-3 font-mono text-gray-800 dark:text-gray-200">
|
||||
{m.source_url ? (
|
||||
<a href={m.source_url} target="_blank" rel="noopener noreferrer" className="text-purple-600 hover:text-purple-800 dark:text-purple-400">
|
||||
{m.identifier}
|
||||
</a>
|
||||
) : (
|
||||
m.identifier
|
||||
)}
|
||||
{m.notes && (
|
||||
<div className="text-[10px] text-gray-500 dark:text-gray-400 mt-0.5 font-sans">{m.notes}</div>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-1 pr-3">
|
||||
<span className={`inline-block px-1.5 py-0.5 rounded ${RELATION_COLORS[m.relation] || 'bg-gray-100 dark:bg-gray-800 text-gray-600'}`}>
|
||||
{m.relation}
|
||||
</span>
|
||||
</td>
|
||||
<td className={`py-1 pr-3 ${CONFIDENCE_COLORS[m.confidence] || 'text-gray-600'}`}>
|
||||
{m.confidence}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="text-[10px] text-gray-400 dark:text-gray-500">
|
||||
Vor Nutzung in einem Drittmarkt durch eine sachkundige Person verifizieren.
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import React, { useMemo, useState, useRef, useEffect } from 'react'
|
||||
import { SearchInput, FilterDropdown, Pagination, ExpandableRow, ExternalLinkIcon } from './LibraryTable'
|
||||
import NormCrossRefPanel from './NormCrossRefPanel'
|
||||
|
||||
export interface Norm {
|
||||
id: string
|
||||
@@ -128,6 +129,7 @@ export default function NormenTab({ norms }: Props) {
|
||||
{n.tags.map((t) => <span key={t} className="px-1.5 py-0.5 rounded text-xs bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300">{t}</span>)}
|
||||
</div>
|
||||
)}
|
||||
<NormCrossRefPanel normId={n.id} />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ObjectivesTab } from './_components/ObjectivesTab'
|
||||
import { AuditsTab } from './_components/AuditsTab'
|
||||
import { ReviewsTab } from './_components/ReviewsTab'
|
||||
import { AssetsTab } from './_components/AssetsTab'
|
||||
import { LicenseModuleBanner } from '@/components/sdk/LicenseModuleBanner'
|
||||
|
||||
// =============================================================================
|
||||
// MAIN PAGE
|
||||
@@ -38,6 +39,13 @@ export default function ISMSPage() {
|
||||
<p className="text-xs text-amber-600 mt-2">
|
||||
Hinweis: Basierend auf eigenen Pruefaspekten, kein ISO-Normtext. Ersetzt kein Zertifizierungsaudit.
|
||||
</p>
|
||||
<div className="mt-3">
|
||||
<LicenseModuleBanner
|
||||
rule={3}
|
||||
sourceLabel="BreakPilot-ISMS-Methodik mit Verweis auf ISO/IEC 27001"
|
||||
detail="ISO-Normtexte sind copyright-geschuetzt (R3 — nur Identifier-Verweise). Eigene Pruefaspekte sind BreakPilot-Eigenwerk."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
// Stufe 1 of the Attribution Renderer (Task #23): the global
|
||||
// "Quellen & Lizenzen" overview. Aggregates all 314k canonical_controls
|
||||
// by their license_rule and shows the source regulations behind each
|
||||
// bucket. Drives the footer link and gives auditors a one-page view of
|
||||
// what licence classes the platform is operating under.
|
||||
|
||||
type SourceCount = {
|
||||
regulation_id: string
|
||||
regulation_name_de: string | null
|
||||
license_rule: number
|
||||
license_type: string | null
|
||||
attribution: string | null
|
||||
jurisdiction: string | null
|
||||
source_type: string | null
|
||||
n_controls: number
|
||||
}
|
||||
|
||||
type RuleBucket = {
|
||||
rule: number
|
||||
label_de: string
|
||||
label_en: string
|
||||
attribution_required: boolean
|
||||
render_full_text: boolean
|
||||
total_controls: number
|
||||
distinct_sources: number
|
||||
sources: SourceCount[]
|
||||
}
|
||||
|
||||
type Overview = {
|
||||
total_controls: number
|
||||
buckets: RuleBucket[]
|
||||
}
|
||||
|
||||
const RULE_COLOR: Record<number, string> = {
|
||||
1: 'border-emerald-200 bg-emerald-50',
|
||||
2: 'border-amber-200 bg-amber-50',
|
||||
3: 'border-slate-200 bg-slate-50',
|
||||
}
|
||||
|
||||
const RULE_BADGE: Record<number, string> = {
|
||||
1: 'bg-emerald-600 text-white',
|
||||
2: 'bg-amber-600 text-white',
|
||||
3: 'bg-slate-600 text-white',
|
||||
}
|
||||
|
||||
export default function LicensesPage() {
|
||||
const [data, setData] = useState<Overview | null>(null)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/sdk/v1/compliance/licenses/overview')
|
||||
.then((r) => (r.ok ? r.json() : Promise.reject(`HTTP ${r.status}`)))
|
||||
.then(setData)
|
||||
.catch((e) => setError(String(e)))
|
||||
}, [])
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<h1 className="text-xl font-semibold mb-2">Quellen & Lizenzen</h1>
|
||||
<p className="text-red-600">Fehler beim Laden: {error}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (!data) {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<h1 className="text-xl font-semibold">Quellen & Lizenzen</h1>
|
||||
<p className="text-slate-500 mt-2">Lade …</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 max-w-7xl">
|
||||
<header className="mb-6">
|
||||
<h1 className="text-2xl font-semibold">Quellen & Lizenzen</h1>
|
||||
<p className="text-sm text-slate-600 mt-1">
|
||||
Diese Plattform stützt sich auf {data.total_controls.toLocaleString('de-DE')}{' '}
|
||||
klassifizierte Compliance-Controls aus den unten genannten Quellen.
|
||||
Jeder Control trägt eine deterministische Lizenzregel (R1–R3), die das
|
||||
Render-Verhalten in Berichten und im Frontend steuert.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section className="mb-8">
|
||||
<h2 className="text-lg font-medium mb-3">Klassifizierungs-Schema</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 text-sm">
|
||||
{data.buckets.map((b) => (
|
||||
<div key={b.rule} className={`rounded border ${RULE_COLOR[b.rule] ?? 'border-slate-200'} p-3`}>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className={`inline-flex items-center justify-center w-7 h-7 rounded-full text-xs font-bold ${RULE_BADGE[b.rule] ?? 'bg-slate-600 text-white'}`}>
|
||||
R{b.rule}
|
||||
</span>
|
||||
<span className="font-medium">{b.label_de}</span>
|
||||
</div>
|
||||
<ul className="text-xs text-slate-700 space-y-1">
|
||||
<li>{b.total_controls.toLocaleString('de-DE')} Controls</li>
|
||||
<li>{b.distinct_sources} Quellen</li>
|
||||
<li>{b.render_full_text ? 'Volltext-Anzeige erlaubt' : 'Nur Identifier-Verweis'}</li>
|
||||
<li>{b.attribution_required ? 'Attribution-Pflicht in Output' : 'keine Attribution-Pflicht'}</li>
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{data.buckets.map((b) => (
|
||||
<section key={b.rule} className="mb-8">
|
||||
<h2 className="text-lg font-medium mb-3 flex items-center gap-2">
|
||||
<span className={`inline-flex items-center justify-center w-7 h-7 rounded-full text-xs font-bold ${RULE_BADGE[b.rule] ?? 'bg-slate-600 text-white'}`}>
|
||||
R{b.rule}
|
||||
</span>
|
||||
{b.label_de}{' '}
|
||||
<span className="text-sm text-slate-500 font-normal">
|
||||
({b.total_controls.toLocaleString('de-DE')} Controls aus {b.distinct_sources} Quellen)
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<div className="overflow-x-auto border rounded">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-slate-100 text-slate-700">
|
||||
<tr>
|
||||
<th className="text-left p-2">Quelle</th>
|
||||
<th className="text-left p-2">Lizenztyp</th>
|
||||
<th className="text-left p-2">Rechtsraum</th>
|
||||
<th className="text-left p-2">Attribution</th>
|
||||
<th className="text-right p-2">Controls</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{b.sources.map((s) => (
|
||||
<tr key={`${b.rule}-${s.regulation_id}`} className="border-t">
|
||||
<td className="p-2">{s.regulation_name_de ?? s.regulation_id}</td>
|
||||
<td className="p-2 text-slate-600">{s.license_type ?? '—'}</td>
|
||||
<td className="p-2 text-slate-600">{s.jurisdiction ?? '—'}</td>
|
||||
<td className="p-2 text-slate-600">{s.attribution ?? '—'}</td>
|
||||
<td className="p-2 text-right tabular-nums">{s.n_controls.toLocaleString('de-DE')}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
|
||||
<footer className="text-xs text-slate-500 border-t pt-4 mt-8">
|
||||
Klassifizierung: deterministisch über parent_control_uuid-Vererbung,
|
||||
control_parent_links → regulation_registry, source_citation,
|
||||
canonical_processed_chunks (Pipeline-Ground-Truth) und LLM-Aggregat-
|
||||
Identifikation für eigene Werke. Audit-Skripte unter
|
||||
breakpilot-core/control-pipeline/scripts/.
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { fetchCriterionTree, type QuaidalControl, type QuaidalCriterionTree } from '../_hooks/useQuaidalData'
|
||||
|
||||
interface Props {
|
||||
sectionId: string
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
function ControlBlock({ ctrl, badgeColor }: { ctrl: QuaidalControl; badgeColor: string }) {
|
||||
return (
|
||||
<div className="border border-gray-200 rounded-lg p-4 bg-white">
|
||||
<div className="flex items-start justify-between gap-3 mb-2">
|
||||
<h4 className="font-semibold text-gray-900">{ctrl.canonical_name}</h4>
|
||||
<span className={`px-2 py-0.5 text-xs rounded-full ${badgeColor} shrink-0`}>{ctrl.source.section}</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 mb-3 whitespace-pre-line">{ctrl.description}</p>
|
||||
{ctrl.source.url && (
|
||||
<a
|
||||
href={ctrl.source.url}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className="text-xs text-purple-600 hover:text-purple-800 underline"
|
||||
>
|
||||
BSI-Quelle ansehen ({ctrl.source.framework})
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function QuaidalCriterionDetail({ sectionId, onClose }: Props) {
|
||||
const [tree, setTree] = useState<QuaidalCriterionTree | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
let active = true
|
||||
setLoading(true)
|
||||
fetchCriterionTree(sectionId).then(t => {
|
||||
if (active) {
|
||||
setTree(t)
|
||||
setLoading(false)
|
||||
}
|
||||
})
|
||||
return () => { active = false }
|
||||
}, [sectionId])
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4">
|
||||
<div className="bg-white rounded-2xl shadow-xl w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-200">
|
||||
<div>
|
||||
<div className="text-xs text-gray-500 uppercase tracking-wide">QUAIDAL Kriterium</div>
|
||||
<h2 className="text-xl font-bold text-gray-900">
|
||||
{tree?.criterion.canonical_name || sectionId}
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="w-8 h-8 rounded-full hover:bg-gray-100 flex items-center justify-center text-gray-500"
|
||||
aria-label="Schliessen"
|
||||
>×</button>
|
||||
</div>
|
||||
|
||||
<div className="overflow-y-auto p-6 space-y-6">
|
||||
{loading && <div className="text-center text-gray-400 py-12">Lade...</div>}
|
||||
|
||||
{tree && (
|
||||
<>
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-2">
|
||||
Anforderung (eigene Formulierung)
|
||||
</h3>
|
||||
<div className="bg-purple-50 border border-purple-200 rounded-lg p-4">
|
||||
<p className="text-gray-800 whitespace-pre-line">{tree.criterion.description}</p>
|
||||
</div>
|
||||
<div className="mt-3 flex flex-wrap items-center gap-3 text-xs text-gray-500">
|
||||
<span>Regulierung: <span className="font-medium text-gray-700">{tree.criterion.regulation_anchor || '—'}</span></span>
|
||||
<span>Quelle: <span className="font-medium text-gray-700">{tree.criterion.source.framework} {tree.criterion.source.section}</span></span>
|
||||
{tree.criterion.source.url && (
|
||||
<a href={tree.criterion.source.url} target="_blank" rel="noreferrer noopener" className="text-purple-600 hover:text-purple-800 underline">
|
||||
Originalquelle
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{tree.criterion.external_refs.length > 0 && (
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-2">
|
||||
Externe Referenzen (nicht ingestiert, nur Verweis)
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{tree.criterion.external_refs.map((ref, i) => (
|
||||
<span key={i} className="px-2 py-1 text-xs bg-gray-100 text-gray-700 rounded">
|
||||
{ref.framework}{ref.citation ? ` — ${ref.citation}` : ''}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tree.building_blocks.length > 0 && (
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-3">
|
||||
Bausteine ({tree.building_blocks.length})
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
{tree.building_blocks.map(qb => (
|
||||
<ControlBlock key={qb.derived_id} ctrl={qb} badgeColor="bg-blue-100 text-blue-700" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tree.measures.length > 0 && (
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-3">
|
||||
Maßnahmen ({tree.measures.length})
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
{tree.measures.map(m => (
|
||||
<ControlBlock key={m.derived_id} ctrl={m} badgeColor="bg-green-100 text-green-700" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tree.metrics.length > 0 && (
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-3">
|
||||
Metriken & Methoden ({tree.metrics.length})
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
{tree.metrics.map(qm => (
|
||||
<ControlBlock key={qm.derived_id} ctrl={qm} badgeColor="bg-amber-100 text-amber-700" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="px-6 py-3 border-t border-gray-200 bg-gray-50 text-xs text-gray-500">
|
||||
Eigene Clean-Room-Ableitung von BSI QUAIDAL. Quellverweis und Lizenz-Note pro Eintrag.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useQuaidalData, type QuaidalControl } from '../_hooks/useQuaidalData'
|
||||
import { QuaidalCriterionDetail } from './QuaidalCriterionDetail'
|
||||
|
||||
function CriterionCard({ ctrl, onOpen }: { ctrl: QuaidalControl; onOpen: () => void }) {
|
||||
return (
|
||||
<button
|
||||
onClick={onOpen}
|
||||
className="text-left bg-white rounded-xl border border-gray-200 p-5 hover:border-purple-400 hover:shadow-sm transition-all"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<h3 className="font-semibold text-gray-900">{ctrl.canonical_name}</h3>
|
||||
<span className="px-2 py-0.5 text-xs rounded-full bg-purple-100 text-purple-700">
|
||||
{ctrl.source.section}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 line-clamp-3">{ctrl.description}</p>
|
||||
<div className="mt-3 flex flex-wrap items-center gap-2 text-xs">
|
||||
<span className="text-gray-500">Bausteine: <span className="font-medium text-gray-700">{ctrl.related_quaidal_ids.length}</span></span>
|
||||
{ctrl.external_refs.slice(0, 2).map((r, i) => (
|
||||
<span key={i} className="px-1.5 py-0.5 bg-gray-100 text-gray-600 rounded">
|
||||
{r.framework}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export function TrainingDataQualityTab() {
|
||||
const { criteria, stats, loading, error } = useQuaidalData()
|
||||
const [openSection, setOpenSection] = useState<string | null>(null)
|
||||
|
||||
if (loading) {
|
||||
return <div className="text-center text-gray-400 py-12">Lade QUAIDAL-Katalog...</div>
|
||||
}
|
||||
if (error) {
|
||||
return (
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4 text-red-700">
|
||||
QUAIDAL-Daten konnten nicht geladen werden: {error}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="bg-purple-50 border border-purple-200 rounded-xl p-5">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Trainingsdaten-Qualität nach BSI QUAIDAL</h2>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Operative Umsetzung von EU AI Act Art. 10 (Datenqualität für Hochrisiko-KI) auf Basis des
|
||||
BSI-Katalogs QUAIDAL. Alle Controls sind eigenständig formuliert (Clean-Room) und verweisen
|
||||
auf die jeweilige QUAIDAL-Sektion.
|
||||
</p>
|
||||
{stats && (
|
||||
<div className="mt-4 grid grid-cols-2 md:grid-cols-4 gap-3 text-sm">
|
||||
<div>
|
||||
<div className="text-xs text-gray-500">Qualitätskriterien</div>
|
||||
<div className="text-xl font-semibold text-gray-900">{stats.counts_by_kind.criterion ?? 0}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-gray-500">Bausteine</div>
|
||||
<div className="text-xl font-semibold text-gray-900">{stats.counts_by_kind.building_block ?? 0}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-gray-500">Maßnahmen</div>
|
||||
<div className="text-xl font-semibold text-gray-900">{stats.counts_by_kind.measure ?? 0}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-gray-500">Metriken & Methoden</div>
|
||||
<div className="text-xl font-semibold text-gray-900">{stats.counts_by_kind.metric ?? 0}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">10 Qualitätskriterien</h3>
|
||||
{criteria.length === 0 ? (
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-8 text-center text-gray-400">
|
||||
Keine Kriterien gefunden. Bitte Backend-Ingest prüfen.
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{criteria.map(c => (
|
||||
<CriterionCard
|
||||
key={c.derived_id}
|
||||
ctrl={c}
|
||||
onOpen={() => setOpenSection(c.source.section)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{stats?.license_note && (
|
||||
<div className="text-xs text-gray-500 italic">{stats.license_note}</div>
|
||||
)}
|
||||
|
||||
{openSection && (
|
||||
<QuaidalCriterionDetail
|
||||
sectionId={openSection}
|
||||
onClose={() => setOpenSection(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
export interface QuaidalExternalRef {
|
||||
framework: string
|
||||
citation: string | null
|
||||
}
|
||||
|
||||
export interface QuaidalSource {
|
||||
framework: string
|
||||
section: string
|
||||
url: string | null
|
||||
commit_sha: string | null
|
||||
title_original: string | null
|
||||
license_note: string | null
|
||||
}
|
||||
|
||||
export interface QuaidalControl {
|
||||
derived_id: string
|
||||
kind: 'criterion' | 'building_block' | 'measure' | 'metric'
|
||||
canonical_name: string
|
||||
description: string
|
||||
regulation_anchor: string | null
|
||||
related_quaidal_ids: string[]
|
||||
external_refs: QuaidalExternalRef[]
|
||||
source: QuaidalSource
|
||||
plagiarism_score: number | null
|
||||
}
|
||||
|
||||
export interface QuaidalStats {
|
||||
counts_by_kind: Record<string, number>
|
||||
source_framework: string
|
||||
source_commit_sha: string | null
|
||||
license_note: string | null
|
||||
}
|
||||
|
||||
export interface QuaidalCriterionTree {
|
||||
criterion: QuaidalControl
|
||||
building_blocks: QuaidalControl[]
|
||||
measures: QuaidalControl[]
|
||||
metrics: QuaidalControl[]
|
||||
}
|
||||
|
||||
const API_BASE = '/api/sdk/v1/quaidal'
|
||||
|
||||
export function useQuaidalData() {
|
||||
const [criteria, setCriteria] = useState<QuaidalControl[]>([])
|
||||
const [stats, setStats] = useState<QuaidalStats | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const loadAll = useCallback(async () => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const [criteriaRes, statsRes] = await Promise.all([
|
||||
fetch(`${API_BASE}/criteria`, { cache: 'no-store' }),
|
||||
fetch(`${API_BASE}/stats`, { cache: 'no-store' }),
|
||||
])
|
||||
if (criteriaRes.ok) {
|
||||
const data = (await criteriaRes.json()) as QuaidalControl[]
|
||||
setCriteria(Array.isArray(data) ? data : [])
|
||||
} else {
|
||||
setError(`Criteria endpoint returned ${criteriaRes.status}`)
|
||||
}
|
||||
if (statsRes.ok) {
|
||||
setStats(await statsRes.json())
|
||||
}
|
||||
} catch (err) {
|
||||
setError(String(err))
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => { loadAll() }, [loadAll])
|
||||
|
||||
return { criteria, stats, loading, error, reload: loadAll }
|
||||
}
|
||||
|
||||
export async function fetchCriterionTree(sectionId: string): Promise<QuaidalCriterionTree | null> {
|
||||
const res = await fetch(`${API_BASE}/criteria/${encodeURIComponent(sectionId)}`, { cache: 'no-store' })
|
||||
if (!res.ok) return null
|
||||
return (await res.json()) as QuaidalCriterionTree
|
||||
}
|
||||
@@ -1,15 +1,23 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import { useSDK } from '@/lib/sdk'
|
||||
import { useQualityData } from './_hooks/useQualityData'
|
||||
import { MetricCard, type QualityMetric } from './_components/MetricCard'
|
||||
import { TestRow } from './_components/TestRow'
|
||||
import { MetricModal } from './_components/MetricModal'
|
||||
import { TestModal } from './_components/TestModal'
|
||||
import { TrainingDataQualityTab } from './_components/TrainingDataQualityTab'
|
||||
|
||||
type TabId = 'model_quality' | 'data_quality'
|
||||
|
||||
export default function QualityPage() {
|
||||
const { state } = useSDK()
|
||||
const searchParams = useSearchParams()
|
||||
const initialTab: TabId = searchParams?.get('category') === 'data_quality' ? 'data_quality' : 'model_quality'
|
||||
const [tab, setTab] = useState<TabId>(initialTab)
|
||||
|
||||
const {
|
||||
metrics,
|
||||
tests,
|
||||
@@ -41,24 +49,54 @@ export default function QualityPage() {
|
||||
<h1 className="text-2xl font-bold text-gray-900">AI Quality Dashboard</h1>
|
||||
<p className="mt-1 text-gray-500">Ueberwachen Sie die Qualitaet und Fairness Ihrer KI-Systeme</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => setShowTestModal(true)}
|
||||
className="flex items-center gap-2 px-4 py-2 border border-purple-300 text-purple-700 rounded-lg hover:bg-purple-50 transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /></svg>
|
||||
Test hinzufuegen
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setEditMetric(undefined); setShowMetricModal(true) }}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" /></svg>
|
||||
Messung hinzufuegen
|
||||
</button>
|
||||
</div>
|
||||
{tab === 'model_quality' && (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => setShowTestModal(true)}
|
||||
className="flex items-center gap-2 px-4 py-2 border border-purple-300 text-purple-700 rounded-lg hover:bg-purple-50 transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /></svg>
|
||||
Test hinzufuegen
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setEditMetric(undefined); setShowMetricModal(true) }}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" /></svg>
|
||||
Messung hinzufuegen
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="border-b border-gray-200">
|
||||
<nav className="-mb-px flex gap-6">
|
||||
<button
|
||||
onClick={() => setTab('model_quality')}
|
||||
className={`pb-3 px-1 text-sm font-medium border-b-2 transition-colors ${
|
||||
tab === 'model_quality'
|
||||
? 'border-purple-500 text-purple-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
Modell-Qualität
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setTab('data_quality')}
|
||||
className={`pb-3 px-1 text-sm font-medium border-b-2 transition-colors ${
|
||||
tab === 'data_quality'
|
||||
? 'border-purple-500 text-purple-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
Trainingsdaten-Qualität (BSI QUAIDAL)
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{tab === 'data_quality' && <TrainingDataQualityTab />}
|
||||
{tab === 'model_quality' && (
|
||||
<>
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<div className="text-sm text-gray-500">Durchschnittlicher Score</div>
|
||||
@@ -141,6 +179,8 @@ export default function QualityPage() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{showMetricModal && (
|
||||
<MetricModal
|
||||
|
||||
@@ -5,6 +5,7 @@ import { SecurityItemCard } from './_components/SecurityItemCard'
|
||||
import { ItemModal } from './_components/ItemModal'
|
||||
import { useSecurityBacklog, EMPTY_NEW_ITEM } from './_hooks/useSecurityBacklog'
|
||||
import type { SecurityItem } from './_hooks/useSecurityBacklog'
|
||||
import { LicenseModuleBanner } from '@/components/sdk/LicenseModuleBanner'
|
||||
|
||||
export default function SecurityBacklogPage() {
|
||||
const [filter, setFilter] = useState<string>('all')
|
||||
@@ -37,6 +38,11 @@ export default function SecurityBacklogPage() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<LicenseModuleBanner
|
||||
rule={2}
|
||||
sourceLabel="OWASP Top 10 / ASVS / SAMM (CC-BY-SA 4.0) + NIST SP 800-53 (US PD)"
|
||||
detail="OWASP-Inhalte zitiert mit Pflicht-Attribution 'OWASP Foundation, CC BY-SA 4.0'. NIST woertlich (R1)."
|
||||
/>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import React from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useTOMGenerator } from '@/lib/sdk/tom-generator'
|
||||
import { TOM_GENERATOR_STEPS } from '@/lib/sdk/tom-generator/types'
|
||||
import { LicenseModuleBanner } from '@/components/sdk/LicenseModuleBanner'
|
||||
|
||||
/**
|
||||
* TOM Generator Landing Page
|
||||
@@ -45,6 +46,14 @@ export default function TOMGeneratorPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-6">
|
||||
<LicenseModuleBanner
|
||||
rule={1}
|
||||
sourceLabel="DSGVO Art. 32 (EU 2016/679) — TOM-Anforderungen"
|
||||
detail="Generator-Logik und Vorlagen sind BreakPilot-Eigenwerk (R3); zitierte Rechtsquelle EU_LAW (R1)."
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Progress Card */}
|
||||
{hasProgress && (
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-6 mb-8">
|
||||
|
||||
@@ -350,7 +350,12 @@ function ActivityCard({ activity, onEdit, onDelete }: { activity: VVTActivity; o
|
||||
<span className="px-2 py-0.5 text-xs bg-purple-100 text-purple-700 rounded-full">DSFA</span>
|
||||
)}
|
||||
{(activity as any).sourceTemplateId && (
|
||||
<span className="px-2 py-0.5 text-xs bg-indigo-100 text-indigo-700 rounded-full">Vorlage</span>
|
||||
<span
|
||||
className="px-2 py-0.5 text-xs bg-indigo-100 text-indigo-700 rounded-full cursor-help"
|
||||
title="Erstellt aus Bundeslaender-DSGVO-Vorlage (Art. 30 DSGVO). Lizenzregel R1 — Hoheitsrecht/DE_LAW, woertlich uebernehmbar."
|
||||
>
|
||||
Vorlage · R1
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<h3 className="text-base font-semibold text-gray-900 truncate">{activity.name || '(Ohne Namen)'}</h3>
|
||||
|
||||
@@ -195,12 +195,18 @@ export default function CatalogTable({
|
||||
)}
|
||||
<td className="px-4 py-2.5">
|
||||
{entry.source === 'system' ? (
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300">
|
||||
System
|
||||
<span
|
||||
className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 cursor-help"
|
||||
title="System-Katalog — Quellen aus EU-Recht, BAuA, NIST u.a. Lizenzregel je Eintrag (siehe /sdk/licenses)."
|
||||
>
|
||||
System · R1/R2/R3
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-300">
|
||||
Benutzerdefiniert
|
||||
<span
|
||||
className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-300 cursor-help"
|
||||
title="Benutzerdefinierter Eintrag — BreakPilot/Anwender-Eigenwerk. Lizenzregel R3 (Identifier-Verweis), keine externe Attribution noetig."
|
||||
>
|
||||
Benutzerdefiniert · R3
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
'use client'
|
||||
|
||||
// Reusable licence-source banner placed at the top of an SDK module page.
|
||||
// One-line context that tells the user (and any auditor) which sources
|
||||
// the module draws on and which BreakPilot licence rule applies.
|
||||
//
|
||||
// Usage:
|
||||
// <LicenseModuleBanner
|
||||
// rule={1}
|
||||
// sourceLabel="DSGVO Art. 30 (EU 2016/679)"
|
||||
// />
|
||||
//
|
||||
// For modules that are pure BreakPilot eigenwerk:
|
||||
// <LicenseModuleBanner rule={3} sourceLabel="BreakPilot-Eigenwerk" />
|
||||
|
||||
type Props = {
|
||||
rule: 1 | 2 | 3
|
||||
sourceLabel: string
|
||||
/** Optional extended note shown after sourceLabel */
|
||||
detail?: string
|
||||
}
|
||||
|
||||
const RULE_META: Record<number, { bg: string; text: string; pill: string; descr: string }> = {
|
||||
1: {
|
||||
bg: 'bg-emerald-50 border-emerald-200',
|
||||
text: 'text-emerald-800',
|
||||
pill: 'bg-emerald-600 text-white',
|
||||
descr: 'Hoheitsrecht/Public Domain — woertlich uebernehmbar',
|
||||
},
|
||||
2: {
|
||||
bg: 'bg-amber-50 border-amber-200',
|
||||
text: 'text-amber-800',
|
||||
pill: 'bg-amber-600 text-white',
|
||||
descr: 'Woertlich mit Attribution-Pflicht',
|
||||
},
|
||||
3: {
|
||||
bg: 'bg-slate-50 border-slate-200',
|
||||
text: 'text-slate-700',
|
||||
pill: 'bg-slate-600 text-white',
|
||||
descr: 'Identifier-Verweis / BreakPilot-Eigenwerk',
|
||||
},
|
||||
}
|
||||
|
||||
export function LicenseModuleBanner({ rule, sourceLabel, detail }: Props) {
|
||||
const m = RULE_META[rule]
|
||||
return (
|
||||
<div className={`px-3 py-2 ${m.bg} border rounded-lg text-xs ${m.text} flex items-start gap-2`}>
|
||||
<span className={`inline-flex items-center justify-center w-6 h-6 rounded-full text-[10px] font-bold ${m.pill} flex-shrink-0`}>
|
||||
R{rule}
|
||||
</span>
|
||||
<div className="flex-1">
|
||||
<span className="font-semibold">Quellen & Lizenz:</span>{' '}
|
||||
<span>{sourceLabel}</span>
|
||||
<span className="text-slate-500"> — {m.descr}.</span>
|
||||
{detail && <span className="block mt-0.5 text-[11px] opacity-80">{detail}</span>}
|
||||
<a href="/sdk/licenses" className="underline ml-1">Quellenverzeichnis</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LicenseModuleBanner
|
||||
@@ -224,6 +224,19 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP
|
||||
<span>Exportieren</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{!collapsed && (
|
||||
<a
|
||||
href="/sdk/licenses"
|
||||
className="mt-2 w-full flex items-center justify-center gap-2 px-4 py-2 text-xs text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
title="Quellen und Lizenzen aller verwendeten Compliance-Controls"
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
<span>Quellen & Lizenzen</span>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</aside>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
// Stufe 3 of the Attribution Renderer (Task #23): an inline source
|
||||
// badge that any rendered control/hazard/measure can attach to itself.
|
||||
//
|
||||
// Visually a small license-rule pill (R1/R2/R3); on hover/click it
|
||||
// reveals the underlying regulation, license type, and — for Rule 2 —
|
||||
// the mandatory attribution string.
|
||||
//
|
||||
// Usage:
|
||||
// <SourceBadge controlUuid={hazard.id} />
|
||||
//
|
||||
// The component lazily fetches /licenses/source-info/{uuid} on first
|
||||
// expand so the surrounding list view stays cheap.
|
||||
|
||||
type SourceInfo = {
|
||||
control_uuid: string
|
||||
license_rule: number | null
|
||||
license_label_de: string | null
|
||||
attribution_required: boolean
|
||||
render_full_text: boolean
|
||||
regulation_id: string | null
|
||||
regulation_name_de: string | null
|
||||
license_type: string | null
|
||||
attribution: string | null
|
||||
source_url: string | null
|
||||
}
|
||||
|
||||
const RULE_BADGE: Record<number, string> = {
|
||||
1: 'bg-emerald-100 text-emerald-800 border-emerald-300',
|
||||
2: 'bg-amber-100 text-amber-800 border-amber-300',
|
||||
3: 'bg-slate-100 text-slate-700 border-slate-300',
|
||||
}
|
||||
|
||||
const RULE_TITLE: Record<number, string> = {
|
||||
1: 'R1 — wörtlich übernehmbar',
|
||||
2: 'R2 — wörtlich mit Attribution',
|
||||
3: 'R3 — nur Identifier zitieren',
|
||||
}
|
||||
|
||||
interface SourceBadgeProps {
|
||||
controlUuid: string
|
||||
/** Optional: skip the fetch and render from already-known data. */
|
||||
prefetched?: SourceInfo
|
||||
/** Compact mode for tight UI rows (smaller pill). */
|
||||
compact?: boolean
|
||||
}
|
||||
|
||||
export function SourceBadge({ controlUuid, prefetched, compact }: SourceBadgeProps) {
|
||||
const [data, setData] = useState<SourceInfo | null>(prefetched ?? null)
|
||||
const [open, setOpen] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || data) return
|
||||
setLoading(true)
|
||||
fetch(`/api/sdk/v1/compliance/licenses/source-info/${controlUuid}`)
|
||||
.then((r) => (r.ok ? r.json() : Promise.reject(`HTTP ${r.status}`)))
|
||||
.then(setData)
|
||||
.catch((e) => setError(String(e)))
|
||||
.finally(() => setLoading(false))
|
||||
}, [open, data, controlUuid])
|
||||
|
||||
const rule = data?.license_rule ?? prefetched?.license_rule ?? null
|
||||
const badgeClass = rule ? RULE_BADGE[rule] ?? RULE_BADGE[3] : 'bg-slate-100 text-slate-500 border-slate-200'
|
||||
const sizeClass = compact ? 'text-[10px] px-1.5 py-0.5' : 'text-xs px-2 py-0.5'
|
||||
|
||||
return (
|
||||
<span className="relative inline-block">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
className={`inline-flex items-center gap-1 rounded border font-medium ${sizeClass} ${badgeClass} hover:opacity-80 transition`}
|
||||
title={rule ? RULE_TITLE[rule] : 'Lizenz unbekannt'}
|
||||
aria-expanded={open}
|
||||
>
|
||||
<svg width="10" height="10" viewBox="0 0 16 16" fill="currentColor" aria-hidden>
|
||||
<path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0Zm0 4.5a1 1 0 1 1 0 2 1 1 0 0 1 0-2ZM7 8h2v4.5H7V8Z" />
|
||||
</svg>
|
||||
{rule ? `R${rule}` : '?'}
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
<div className="absolute left-0 mt-1 z-40 w-80 rounded-md border border-slate-200 bg-white shadow-lg p-3 text-xs">
|
||||
{loading && <p className="text-slate-500">Lade Quellen-Info…</p>}
|
||||
{error && <p className="text-red-600">Fehler: {error}</p>}
|
||||
{data && (
|
||||
<div className="space-y-2">
|
||||
<div className="font-semibold text-slate-800">
|
||||
{data.license_label_de ?? 'Lizenz unbekannt'}
|
||||
</div>
|
||||
{data.regulation_name_de && (
|
||||
<div>
|
||||
<span className="text-slate-500">Quelle:</span>{' '}
|
||||
<span className="text-slate-800">{data.regulation_name_de}</span>
|
||||
</div>
|
||||
)}
|
||||
{data.license_type && (
|
||||
<div>
|
||||
<span className="text-slate-500">Lizenztyp:</span>{' '}
|
||||
<span className="text-slate-700">{data.license_type}</span>
|
||||
</div>
|
||||
)}
|
||||
{data.attribution && (
|
||||
<div className="rounded bg-amber-50 border border-amber-200 px-2 py-1.5">
|
||||
<div className="text-[10px] font-semibold text-amber-800 uppercase tracking-wide">
|
||||
Attribution-Pflicht
|
||||
</div>
|
||||
<div className="text-amber-900">{data.attribution}</div>
|
||||
</div>
|
||||
)}
|
||||
{!data.render_full_text && (
|
||||
<div className="text-[10px] text-slate-500 italic">
|
||||
Volltext wird im Output nicht gerendert — nur Identifier-Verweis.
|
||||
</div>
|
||||
)}
|
||||
{data.source_url && (
|
||||
<a
|
||||
href={data.source_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-block text-[10px] text-blue-600 hover:underline mt-1"
|
||||
>
|
||||
Originalquelle öffnen ↗
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default SourceBadge
|
||||
@@ -0,0 +1,355 @@
|
||||
/**
|
||||
* E2E-Test fuer den Founding-Wizard
|
||||
*
|
||||
* Prueft den vollstaendigen 8-Step-Flow:
|
||||
* - Application-Errors / Console-Errors auf jeder Seite
|
||||
* - StepBasics: Prefill-Button + Registergericht/HRB-Felder
|
||||
* - StepGesellschafter: Rollen-Dropdown + IP-Bereiche fuer 2 Gruender
|
||||
* - Per-Person Generation: 2 IP-Assignment-Dokumente
|
||||
* - localStorage-Persistenz
|
||||
*
|
||||
* Backend wird per route.fulfill() gemockt — Test ist hermetisch.
|
||||
*/
|
||||
|
||||
import { test, expect, type Page, type ConsoleMessage } from '@playwright/test'
|
||||
|
||||
const BASE = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:3002'
|
||||
const WIZARD_PATH = '/sdk/founding-wizard'
|
||||
|
||||
/** Filtert Browser-Console auf echte App-Errors (ignoriert Next.js / Hydration / 3rd-party Warnings). */
|
||||
function isRealAppError(msg: ConsoleMessage): boolean {
|
||||
if (msg.type() !== 'error') return false
|
||||
const text = msg.text()
|
||||
// Bekanntes Rauschen ausschliessen
|
||||
const ignored = [
|
||||
'Failed to load resource', // 404 fuer Icons etc.
|
||||
'Download the React DevTools', // React-Hinweis
|
||||
'net::ERR_', // Netzwerk (gemockt → erwartete Misses)
|
||||
'Hydration failed because', // Next 15 Pseudo-Errors bei dev
|
||||
'[founding-wizard] prefill failed', // Intentional UX-Logging im Prefill-Fehlerpfad
|
||||
]
|
||||
return !ignored.some(p => text.includes(p))
|
||||
}
|
||||
|
||||
const IGNORED_PAGE_ERRORS = [
|
||||
// Hydration mismatches durch dynamische Zeitstempel ("Gerade eben" vs "vor 1 Min")
|
||||
// im SDK-Header — pure dev-Mode-Symptom, kein App-Bug.
|
||||
'Hydration failed because the server rendered text didn',
|
||||
'There was an error while hydrating',
|
||||
// Next.js dev-mode signals fuer Hydration-Issues
|
||||
'Text content does not match server-rendered HTML',
|
||||
]
|
||||
|
||||
function isIgnoredPageError(err: Error): boolean {
|
||||
return IGNORED_PAGE_ERRORS.some(p => err.message.includes(p))
|
||||
}
|
||||
|
||||
/** Setzt Console-Error- und PageError-Listener. Wirft am Ende, wenn welche aufgetreten sind. */
|
||||
function installErrorTraps(page: Page): { assertNoErrors: () => void } {
|
||||
const consoleErrors: string[] = []
|
||||
const pageErrors: string[] = []
|
||||
|
||||
page.on('console', msg => {
|
||||
if (isRealAppError(msg)) consoleErrors.push(msg.text())
|
||||
})
|
||||
page.on('pageerror', err => {
|
||||
if (!isIgnoredPageError(err)) pageErrors.push(`${err.name}: ${err.message}`)
|
||||
})
|
||||
|
||||
return {
|
||||
assertNoErrors() {
|
||||
const all = [...pageErrors.map(e => `[pageerror] ${e}`), ...consoleErrors.map(e => `[console.error] ${e}`)]
|
||||
if (all.length > 0) {
|
||||
throw new Error(`Application-Errors waehrend des Flows:\n${all.join('\n')}`)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/** Mockt die zwei API-Endpoints, die der Wizard aufruft. */
|
||||
async function mockBackend(page: Page) {
|
||||
// 1) Company-Profile Prefill
|
||||
await page.route('**/api/sdk/v1/company-profile**', async route => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
profile: {
|
||||
companyName: 'Breakpilot GmbH',
|
||||
legalForm: 'GmbH',
|
||||
industry: ['Software', 'KI/ML'],
|
||||
businessModel: 'SaaS',
|
||||
offerings: ['SaaS-Plattform', 'Compliance-API'],
|
||||
headquartersStreet: 'Königstraße 1',
|
||||
headquartersZip: '70173',
|
||||
headquartersCity: 'Stuttgart',
|
||||
},
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
// 2) Founding-Wizard Generate (gibt 9 Dokumente zurueck: 7 normale + 2 per-person IP-Assignments)
|
||||
await page.route('**/api/v1/founding-wizard/generate', async route => {
|
||||
const request = route.request()
|
||||
const body = JSON.parse(request.postData() || '{}')
|
||||
const selected: string[] = body.selected_documents || []
|
||||
const gesellschafter: Array<{ name?: string; is_geschaeftsfuehrer?: boolean }> = body.gesellschafter || []
|
||||
|
||||
const PER_PERSON = ['ip_assignment_agreement', 'managing_director_employment_contract']
|
||||
const docs: unknown[] = []
|
||||
const tinyDocx = 'UEsDBBQAAAAIAA==' // gueltige base64-Stub (Playwright braucht keinen echten DOCX)
|
||||
|
||||
for (const docType of selected) {
|
||||
if (PER_PERSON.includes(docType)) {
|
||||
const persons = docType === 'managing_director_employment_contract'
|
||||
? gesellschafter.filter(g => g.is_geschaeftsfuehrer)
|
||||
: gesellschafter
|
||||
for (const p of persons) {
|
||||
docs.push({
|
||||
document_type: docType,
|
||||
title: `${docType} — ${p.name}`,
|
||||
filename: `${docType}_${(p.name || 'X').replace(/\s/g, '_')}.docx`,
|
||||
download_url: `data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,${tinyDocx}`,
|
||||
size_bytes: 12345,
|
||||
generated_at: '2026-05-21T12:00:00Z',
|
||||
})
|
||||
}
|
||||
} else {
|
||||
docs.push({
|
||||
document_type: docType,
|
||||
title: docType,
|
||||
filename: `${docType}.docx`,
|
||||
download_url: `data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,${tinyDocx}`,
|
||||
size_bytes: 12345,
|
||||
generated_at: '2026-05-21T12:00:00Z',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ documents: docs, warnings: [] }),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/** Clears wizard-state and pre-accepts cookies so the CookieBannerOverlay
|
||||
* does not intercept clicks during the test. */
|
||||
async function resetWizardState(page: Page) {
|
||||
await page.addInitScript(() => {
|
||||
try {
|
||||
window.localStorage.removeItem('breakpilot:founding-wizard:state:v1')
|
||||
// CookieBannerOverlay liest 'bp-sdk-cookie-consent' und blendet sich aus,
|
||||
// sobald ein Eintrag existiert. Wir setzen Minimal-Consent.
|
||||
window.localStorage.setItem('bp-sdk-cookie-consent', JSON.stringify({
|
||||
necessary: true, statistics: false, marketing: false, functional: false,
|
||||
ewrOnly: false, blockedVendors: [], timestamp: new Date().toISOString(),
|
||||
}))
|
||||
} catch {}
|
||||
})
|
||||
}
|
||||
|
||||
test.describe('Founding-Wizard E2E', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await resetWizardState(page)
|
||||
await mockBackend(page)
|
||||
})
|
||||
|
||||
test('vollstaendiger 8-Step-Flow ohne Application-Errors', async ({ page }) => {
|
||||
const errors = installErrorTraps(page)
|
||||
|
||||
await page.goto(`${BASE}${WIZARD_PATH}`)
|
||||
await expect(page.getByTestId('founding-wizard')).toBeVisible()
|
||||
await expect(page.getByTestId('step-content-1')).toBeVisible()
|
||||
|
||||
// --- Step 1: Basics + Prefill ---
|
||||
await page.getByRole('button', { name: /Aus Unternehmensprofil vorbef/i }).click()
|
||||
await expect(page.getByTestId('company-name')).toHaveValue('Breakpilot GmbH', { timeout: 5000 })
|
||||
await expect(page.getByTestId('company-seat')).toHaveValue('Stuttgart')
|
||||
|
||||
// Pflichtfeld: company_purpose_description (mind. 10 Zeichen)
|
||||
await page.getByTestId('company-purpose').fill(
|
||||
'die Entwicklung, Bereitstellung und der Betrieb von KI-gestuetzten Compliance-Werkzeugen sowie damit verbundener Beratungsleistungen.'
|
||||
)
|
||||
|
||||
// Neue Felder: Registergericht + HRB
|
||||
await page.getByTestId('register-court').fill('Amtsgericht Stuttgart')
|
||||
await page.getByTestId('hrb-number').fill('') // noch nicht eingetragen
|
||||
|
||||
await page.getByTestId('next-step').click()
|
||||
|
||||
// --- Step 2: Gesellschafter ---
|
||||
await expect(page.getByTestId('step-content-2')).toBeVisible()
|
||||
|
||||
// Benjamin (CEO, IP: Compliance + RAG)
|
||||
await page.getByTestId('gs-name').fill('Benjamin Bönisch')
|
||||
await page.getByTestId('gs-birthdate').fill('1985-01-15')
|
||||
await page.getByTestId('gs-address').fill('Teststraße 1, 70173 Stuttgart')
|
||||
await page.getByTestId('gs-email').fill('benjamin@breakpilot.ai')
|
||||
await page.getByTestId('gs-nennbetrag').fill('12500')
|
||||
await page.getByTestId('gs-role').selectOption('CEO')
|
||||
await page.getByTestId('gs-ip-areas').fill(
|
||||
'Compliance-Engine (Quellcode + Architektur)\nRAG-Pipeline\nProdukt-Konzepte'
|
||||
)
|
||||
await page.getByTestId('add-gesellschafter').click()
|
||||
await expect(page.getByTestId('gs-row-1')).toBeVisible()
|
||||
|
||||
// Sharang (CTO, IP: Security + Infrastruktur)
|
||||
await page.getByTestId('gs-name').fill('Sharang Parnerkar')
|
||||
await page.getByTestId('gs-birthdate').fill('1990-06-20')
|
||||
await page.getByTestId('gs-address').fill('Teststraße 2, 70173 Stuttgart')
|
||||
await page.getByTestId('gs-email').fill('sharang@breakpilot.ai')
|
||||
await page.getByTestId('gs-nennbetrag').fill('12500')
|
||||
await page.getByTestId('gs-role').selectOption('CTO')
|
||||
await page.getByTestId('gs-ip-areas').fill('Security-Modul\nInfrastructure-as-Code')
|
||||
await page.getByTestId('add-gesellschafter').click()
|
||||
await expect(page.getByTestId('gs-row-2')).toBeVisible()
|
||||
|
||||
// Summe Nennbetraege muss Stammkapital entsprechen (25.000)
|
||||
await expect(page.getByTestId('gs-total')).toContainText('25.000')
|
||||
|
||||
await page.getByTestId('next-step').click()
|
||||
|
||||
// --- Step 3: GF-Assignment (Defaults sind ok, beide bereits GF) ---
|
||||
await expect(page.getByTestId('step-content-3')).toBeVisible()
|
||||
await expect(page.getByTestId('gf-assignment-table')).toBeVisible()
|
||||
await page.getByTestId('next-step').click()
|
||||
|
||||
// --- Step 4: Kapital (Defaults: 25000) ---
|
||||
await expect(page.getByTestId('step-content-4')).toBeVisible()
|
||||
await expect(page.getByTestId('stammkapital')).toHaveValue('25000')
|
||||
await page.getByTestId('next-step').click()
|
||||
|
||||
// --- Step 5: Notar ---
|
||||
await expect(page.getByTestId('step-content-5')).toBeVisible()
|
||||
await page.getByTestId('notary-name').fill('Dr. Max Mustermann')
|
||||
await page.getByTestId('notary-place').fill('Stuttgart')
|
||||
await page.getByTestId('notary-address').fill('Königstraße 99, 70173 Stuttgart')
|
||||
await page.getByTestId('notarial-date').fill('2026-06-15')
|
||||
await page.getByTestId('next-step').click()
|
||||
|
||||
// --- Step 6: SHA-Optionen (Defaults sind ok) ---
|
||||
await expect(page.getByTestId('step-content-6')).toBeVisible()
|
||||
await expect(page.getByTestId('has-sha')).toBeChecked()
|
||||
await page.getByTestId('next-step').click()
|
||||
|
||||
// --- Step 7: GF-Vertraege (fuer jeden GF einen) ---
|
||||
await expect(page.getByTestId('step-content-7')).toBeVisible()
|
||||
// Beide GF-Contract-Karten muessen sichtbar sein
|
||||
const contractCards = page.locator('[data-testid^="contract-"]')
|
||||
await expect(contractCards).toHaveCount(2)
|
||||
// Salary in beiden Cards anfassen → registriert Contracts (canProceed-Bedingung).
|
||||
// Wir setzen einen anderen Wert als Default (84000) damit React onChange feuert.
|
||||
const salaryInputs = page.locator('[data-testid^="salary-"]')
|
||||
const salaryCount = await salaryInputs.count()
|
||||
for (let i = 0; i < salaryCount; i++) {
|
||||
await salaryInputs.nth(i).fill('90000')
|
||||
}
|
||||
// Warten bis "Weiter" enabled ist
|
||||
await expect(page.getByTestId('next-step')).toBeEnabled()
|
||||
await page.getByTestId('next-step').click()
|
||||
|
||||
// --- Step 8: Generate ---
|
||||
await expect(page.getByTestId('step-content-8')).toBeVisible()
|
||||
await expect(page.getByTestId('generate-summary')).toContainText('Breakpilot GmbH')
|
||||
await expect(page.getByTestId('generate-summary')).toContainText('2', { useInnerText: true })
|
||||
|
||||
// Notartermin-Bundle auswaehlen
|
||||
await page.getByTestId('select-notary-bundle').click()
|
||||
|
||||
// Generieren (Backend gemockt)
|
||||
await page.getByTestId('generate-docs').click()
|
||||
|
||||
// Generated-Docs-Block muss erscheinen
|
||||
await expect(page.getByTestId('generated-docs')).toBeVisible({ timeout: 10000 })
|
||||
|
||||
// Per-Person Verifikation: zwei IP-Assignment-Downloads erwartet
|
||||
const ipDownloads = page.locator('[data-testid="download-ip_assignment_agreement"]')
|
||||
await expect(ipDownloads).toHaveCount(2)
|
||||
|
||||
// Per-Person Verifikation: zwei GF-Vertraege erwartet
|
||||
const gfDownloads = page.locator('[data-testid="download-managing_director_employment_contract"]')
|
||||
await expect(gfDownloads).toHaveCount(2)
|
||||
|
||||
// Kein generate-error sichtbar
|
||||
await expect(page.getByTestId('generate-error')).toBeHidden()
|
||||
|
||||
// Final: keine Errors auf der Konsole
|
||||
errors.assertNoErrors()
|
||||
})
|
||||
|
||||
test('Prefill-Button setzt Fehler bei Backend-Fehler ohne Application-Error', async ({ page }) => {
|
||||
// Spezial-Mock: company-profile gibt 500 zurueck
|
||||
await page.route('**/api/sdk/v1/company-profile**', async route => {
|
||||
await route.fulfill({ status: 500, body: 'boom' })
|
||||
})
|
||||
|
||||
const errors = installErrorTraps(page)
|
||||
await page.goto(`${BASE}${WIZARD_PATH}`)
|
||||
|
||||
await page.getByRole('button', { name: /Aus Unternehmensprofil vorbef/i }).click()
|
||||
// UI muss Fehlermeldung anzeigen, NICHT crashen
|
||||
await expect(page.getByText('Konnte Unternehmensprofil nicht laden')).toBeVisible()
|
||||
|
||||
errors.assertNoErrors()
|
||||
})
|
||||
|
||||
test('Step-Navigation: Zurueck und Reset funktionieren ohne Errors', async ({ page }) => {
|
||||
const errors = installErrorTraps(page)
|
||||
await page.goto(`${BASE}${WIZARD_PATH}`)
|
||||
|
||||
// Minimum Step 1 fuellen
|
||||
await page.getByTestId('company-name').fill('Breakpilot GmbH')
|
||||
await page.getByTestId('company-seat').fill('Stuttgart')
|
||||
await page.getByTestId('company-purpose').fill('die Entwicklung von Compliance-Software fuer Unternehmen.')
|
||||
|
||||
await page.getByTestId('next-step').click()
|
||||
await expect(page.getByTestId('step-content-2')).toBeVisible()
|
||||
|
||||
// Zurueck
|
||||
await page.getByTestId('prev-step').click()
|
||||
await expect(page.getByTestId('step-content-1')).toBeVisible()
|
||||
|
||||
// Eingaben muessen erhalten geblieben sein (localStorage-persistence)
|
||||
await expect(page.getByTestId('company-name')).toHaveValue('Breakpilot GmbH')
|
||||
|
||||
// Reset (mit Dialog-Bestaetigung)
|
||||
page.once('dialog', dialog => dialog.accept())
|
||||
await page.getByTestId('reset-wizard').click()
|
||||
await expect(page.getByTestId('company-name')).toHaveValue('')
|
||||
|
||||
errors.assertNoErrors()
|
||||
})
|
||||
|
||||
test('IP-Areas + Rollen-Dropdown in Step 2', async ({ page }) => {
|
||||
const errors = installErrorTraps(page)
|
||||
await page.goto(`${BASE}${WIZARD_PATH}`)
|
||||
|
||||
// Step 1 zuegig fuellen
|
||||
await page.getByTestId('company-name').fill('Breakpilot GmbH')
|
||||
await page.getByTestId('company-seat').fill('Stuttgart')
|
||||
await page.getByTestId('company-purpose').fill('die Entwicklung von Compliance-Software fuer Unternehmen.')
|
||||
await page.getByTestId('next-step').click()
|
||||
|
||||
// Rollen-Dropdown muss ein <select> sein, nicht <input>
|
||||
const role = page.getByTestId('gs-role')
|
||||
await expect(role).toHaveJSProperty('tagName', 'SELECT')
|
||||
|
||||
// CEO-Option waehlbar
|
||||
await page.getByTestId('gs-name').fill('Benjamin Bönisch')
|
||||
await page.getByTestId('gs-address').fill('Test 1')
|
||||
await page.getByTestId('gs-nennbetrag').fill('25000')
|
||||
await role.selectOption('CEO')
|
||||
await page.getByTestId('gs-ip-areas').fill('Compliance-Engine\nRAG-Pipeline')
|
||||
await page.getByTestId('add-gesellschafter').click()
|
||||
|
||||
// Tabelle muss IP-Bereiche anzeigen
|
||||
const row = page.getByTestId('gs-row-1')
|
||||
await expect(row).toContainText('Benjamin Bönisch')
|
||||
await expect(row).toContainText('CEO')
|
||||
await expect(row).toContainText('Compliance-Engine')
|
||||
|
||||
errors.assertNoErrors()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Template-Kategorisierung als Code-Registry.
|
||||
*
|
||||
* Source-of-Truth bei aktiver Migration 137/138 ist die DB.
|
||||
* Diese Registry dient als Fallback und für Frontend-only Filter,
|
||||
* wenn DB-Felder noch nicht verfügbar sind (z.B. lokale Dev-DB ohne Migration).
|
||||
*
|
||||
* Synchron halten mit migrations/138_template_backfill_categories.sql.
|
||||
*/
|
||||
|
||||
export type LifecycleStage = 'pre_founding' | 'founding' | 'startup' | 'kmu' | 'konzern'
|
||||
|
||||
export type FunctionalCategory =
|
||||
| 'founding_legal'
|
||||
| 'employment'
|
||||
| 'investor_funding'
|
||||
| 'customer_b2b'
|
||||
| 'customer_b2c'
|
||||
| 'data_protection'
|
||||
| 'it_security'
|
||||
| 'ai_governance'
|
||||
| 'internal_policy'
|
||||
| 'public_facing'
|
||||
| 'compliance_process'
|
||||
| 'finance_tax'
|
||||
| 'vendor_supplier'
|
||||
|
||||
export interface TemplateCategorization {
|
||||
lifecycle_stage: LifecycleStage[]
|
||||
functional_category: FunctionalCategory
|
||||
}
|
||||
|
||||
export const TEMPLATE_CATEGORIES: Record<string, TemplateCategorization> = {
|
||||
// Founding Legal
|
||||
gesellschafterliste: { lifecycle_stage: ['pre_founding', 'founding'], functional_category: 'founding_legal' },
|
||||
gf_bestellungsbeschluss: { lifecycle_stage: ['founding'], functional_category: 'founding_legal' },
|
||||
hrb_anmeldung: { lifecycle_stage: ['founding'], functional_category: 'founding_legal' },
|
||||
ip_assignment_agreement: { lifecycle_stage: ['pre_founding', 'founding', 'startup'], functional_category: 'founding_legal' },
|
||||
articles_of_association: { lifecycle_stage: ['founding', 'startup', 'kmu', 'konzern'], functional_category: 'founding_legal' },
|
||||
sha: { lifecycle_stage: ['founding', 'startup', 'kmu', 'konzern'], functional_category: 'founding_legal' },
|
||||
geschaeftsordnung_gf: { lifecycle_stage: ['founding', 'startup', 'kmu', 'konzern'], functional_category: 'founding_legal' },
|
||||
|
||||
// Investor / Funding
|
||||
term_sheet: { lifecycle_stage: ['pre_founding', 'startup'], functional_category: 'investor_funding' },
|
||||
convertible_loan_agreement: { lifecycle_stage: ['pre_founding', 'startup'], functional_category: 'investor_funding' },
|
||||
subscription_agreement: { lifecycle_stage: ['startup', 'kmu'], functional_category: 'investor_funding' },
|
||||
esop_plan: { lifecycle_stage: ['startup', 'kmu'], functional_category: 'investor_funding' },
|
||||
cap_table: { lifecycle_stage: ['founding', 'startup', 'kmu', 'konzern'], functional_category: 'investor_funding' },
|
||||
|
||||
// Employment
|
||||
managing_director_employment_contract: { lifecycle_stage: ['founding', 'startup', 'kmu', 'konzern'], functional_category: 'employment' },
|
||||
employment_contract_de: { lifecycle_stage: ['founding', 'startup', 'kmu', 'konzern'], functional_category: 'employment' },
|
||||
nda: { lifecycle_stage: ['founding', 'startup', 'kmu', 'konzern'], functional_category: 'employment' },
|
||||
offboarding_policy: { lifecycle_stage: ['founding', 'startup', 'kmu', 'konzern'], functional_category: 'employment' },
|
||||
|
||||
// Customer B2B
|
||||
agb: { lifecycle_stage: ['startup', 'kmu', 'konzern'], functional_category: 'customer_b2b' },
|
||||
sla: { lifecycle_stage: ['startup', 'kmu', 'konzern'], functional_category: 'customer_b2b' },
|
||||
dpa: { lifecycle_stage: ['startup', 'kmu', 'konzern'], functional_category: 'customer_b2b' },
|
||||
data_processing_agreement: { lifecycle_stage: ['startup', 'kmu', 'konzern'], functional_category: 'customer_b2b' },
|
||||
cloud_service_agreement: { lifecycle_stage: ['startup', 'kmu', 'konzern'], functional_category: 'customer_b2b' },
|
||||
terms_of_service: { lifecycle_stage: ['startup', 'kmu', 'konzern'], functional_category: 'customer_b2b' },
|
||||
|
||||
// Public-facing
|
||||
impressum: { lifecycle_stage: ['founding', 'startup', 'kmu', 'konzern'], functional_category: 'public_facing' },
|
||||
|
||||
// AI Governance
|
||||
ai_usage_policy: { lifecycle_stage: ['startup', 'kmu', 'konzern'], functional_category: 'ai_governance' },
|
||||
|
||||
// Whistleblower nur ab KMU (>=50 MA)
|
||||
whistleblower_policy: { lifecycle_stage: ['kmu', 'konzern'], functional_category: 'internal_policy' },
|
||||
}
|
||||
|
||||
/**
|
||||
* Notartermin-Bundle: alle Dokumente die für die Gründung benötigt werden.
|
||||
* Investor-Dokumente sind separat (term_sheet, convertible_loan_agreement, etc.).
|
||||
*/
|
||||
export const NOTARY_BUNDLE_DOCUMENTS: string[] = [
|
||||
'articles_of_association', // Satzung — notariell beurkundet
|
||||
'gesellschafterliste', // Pflicht § 40 GmbHG
|
||||
'gf_bestellungsbeschluss', // Bestellung Geschäftsführer
|
||||
'hrb_anmeldung', // HRB-Anmeldung
|
||||
'sha', // optional parallel
|
||||
'geschaeftsordnung_gf', // intern, nach Notar
|
||||
'managing_director_employment_contract', // GF-Dienstverträge
|
||||
'ip_assignment_agreement', // Gründer-IP sichern
|
||||
]
|
||||
|
||||
export function getDocumentsForStage(stage: LifecycleStage): string[] {
|
||||
return Object.entries(TEMPLATE_CATEGORIES)
|
||||
.filter(([, cat]) => cat.lifecycle_stage.includes(stage))
|
||||
.map(([docType]) => docType)
|
||||
}
|
||||
|
||||
export function getDocumentsForCategory(category: FunctionalCategory): string[] {
|
||||
return Object.entries(TEMPLATE_CATEGORIES)
|
||||
.filter(([, cat]) => cat.functional_category === category)
|
||||
.map(([docType]) => docType)
|
||||
}
|
||||
|
||||
export const LIFECYCLE_STAGE_LABELS: Record<LifecycleStage, string> = {
|
||||
pre_founding: 'Vor-Gründung (Term Sheet, IP-Sicherung)',
|
||||
founding: 'Gründung (Notar)',
|
||||
startup: 'Startup (0-3 Jahre, <25 MA)',
|
||||
kmu: 'KMU (3+ Jahre, 25-250 MA)',
|
||||
konzern: 'Konzern (250+ MA)',
|
||||
}
|
||||
|
||||
export const FUNCTIONAL_CATEGORY_LABELS: Record<FunctionalCategory, string> = {
|
||||
founding_legal: 'Gründungsrechtliches',
|
||||
employment: 'Arbeitsverträge',
|
||||
investor_funding: 'Investor & Funding',
|
||||
customer_b2b: 'Kunden-Verträge (B2B)',
|
||||
customer_b2c: 'Kunden-Verträge (B2C)',
|
||||
data_protection: 'Datenschutz (DSGVO)',
|
||||
it_security: 'IT-Sicherheit',
|
||||
ai_governance: 'KI-Governance',
|
||||
internal_policy: 'Interne Richtlinien',
|
||||
public_facing: 'Öffentlich (Website)',
|
||||
compliance_process:'Compliance-Prozesse',
|
||||
finance_tax: 'Finanzen & Steuern',
|
||||
vendor_supplier: 'Lieferanten',
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* TypeScript-Datentypen für den Founding-Wizard.
|
||||
*
|
||||
* Die Wizard-Eingaben werden in localStorage gespeichert und beim Submit
|
||||
* an die document-generator API geschickt zur Template-Befüllung.
|
||||
*/
|
||||
|
||||
import type { LifecycleStage } from './template-categories'
|
||||
|
||||
export interface Gesellschafter {
|
||||
id: string
|
||||
rolle: 'founder' | 'investor' | 'family' | 'other'
|
||||
name: string
|
||||
geburtsdatum?: string // YYYY-MM-DD
|
||||
adresse: string
|
||||
email?: string
|
||||
/** Nennbetrag in EUR, z.B. 25000 */
|
||||
nennbetrag_eur: number
|
||||
/** Anteilsnummer beginnend bei 1 */
|
||||
anteil_nr: number
|
||||
/** prozentualer Anteil am Stammkapital (computed) */
|
||||
anteil_pct?: number
|
||||
is_geschaeftsfuehrer: boolean
|
||||
/** Bei GF: interne Rolle z.B. CEO/CTO */
|
||||
internal_role?: string
|
||||
/** Falls Gründer akademischen Hintergrund hat (Professur etc.) */
|
||||
has_academic_background?: boolean
|
||||
/** IP-Bereiche die der Gründer für die GmbH einbringt (z.B. ["Compliance-Engine", "RAG-Pipeline"]) */
|
||||
ip_areas?: string[]
|
||||
}
|
||||
|
||||
export interface NotarData {
|
||||
notary_name: string
|
||||
notary_place: string
|
||||
notary_address?: string
|
||||
notary_email?: string
|
||||
notarial_date?: string // YYYY-MM-DD, geplant
|
||||
urnr?: string // wird vom Notar vergeben
|
||||
}
|
||||
|
||||
export interface CompanyBasics {
|
||||
company_name: string
|
||||
legal_form: 'GmbH' | 'UG'
|
||||
company_seat: string // z.B. "Bietigheim-Bissingen"
|
||||
company_address: string
|
||||
company_purpose_description: string // Volltext für § 2 Satzung
|
||||
company_purpose_bullets: string[]
|
||||
industry: string
|
||||
business_year: string // z.B. "Kalenderjahr"
|
||||
has_research_focus: boolean
|
||||
/** Registergericht (z.B. "Amtsgericht Stuttgart"). Pflicht für HRB-Anmeldung. */
|
||||
register_court?: string
|
||||
/** HRB-Nummer (z.B. "HRB 12345"). Leer falls noch nicht eingetragen. */
|
||||
hrb_number?: string
|
||||
}
|
||||
|
||||
export interface CapitalConfig {
|
||||
stammkapital_eur: number // z.B. 25000
|
||||
einlage_method: 'Geld' | 'Sacheinlage' | 'Geld und Sacheinlage'
|
||||
einlage_quote_initial_pct: number // z.B. 50 oder 100
|
||||
has_sacheinlage: boolean
|
||||
}
|
||||
|
||||
export interface SHAConfig {
|
||||
has_sha: boolean
|
||||
vesting_months: number // Standard 48
|
||||
cliff_months: number // Standard 12
|
||||
drag_along_threshold_pct: number // Standard 75
|
||||
tag_along_threshold_pct: number // Standard 20
|
||||
reserved_matters_majority_pct: number // Standard 75
|
||||
has_beirat: boolean
|
||||
has_texas_shootout: boolean
|
||||
has_ceo_designation: boolean
|
||||
ceo_name?: string // ref to gesellschafter.name
|
||||
esop_pool_pct: number // Standard 0 oder 10
|
||||
}
|
||||
|
||||
export interface GFContract {
|
||||
gesellschafter_id: string // ref to gesellschafter.id
|
||||
gross_annual_salary_eur: number
|
||||
has_bonus: boolean
|
||||
has_company_car: boolean
|
||||
has_bav: boolean
|
||||
vacation_days: number // Standard 30
|
||||
kuendigungsfrist_gesellschaft_monate: number // Standard 6
|
||||
kuendigungsfrist_gf_monate: number // Standard 3
|
||||
para_181_release: boolean
|
||||
sv_status: 'sozialversicherungsfrei' | 'sozialversicherungspflichtig' | 'noch zu klären'
|
||||
}
|
||||
|
||||
/**
|
||||
* Vollständiger Wizard-State.
|
||||
* Wird Step-by-Step befüllt, in localStorage gespeichert,
|
||||
* und beim Submit an /api/v1/founding-wizard/generate geschickt.
|
||||
*/
|
||||
export interface FoundingWizardState {
|
||||
/** Aktueller Step (1-8) */
|
||||
current_step: number
|
||||
/** Lifecycle-Stage Auswahl (default: founding) */
|
||||
lifecycle_stage: LifecycleStage
|
||||
|
||||
// Step 1: Lifecycle
|
||||
is_pre_notary: boolean
|
||||
|
||||
// Step 2: Basics
|
||||
basics: CompanyBasics
|
||||
|
||||
// Step 3: Gesellschafter
|
||||
gesellschafter: Gesellschafter[]
|
||||
|
||||
// Step 4: Kapital
|
||||
capital: CapitalConfig
|
||||
|
||||
// Step 5: Notar
|
||||
notar: NotarData
|
||||
|
||||
// Step 6: SHA-Konfiguration
|
||||
sha: SHAConfig
|
||||
|
||||
// Step 7: GF-Verträge (1 pro GF)
|
||||
gf_contracts: GFContract[]
|
||||
|
||||
// Step 8: Auswahl der zu generierenden Dokumente
|
||||
selected_documents: string[]
|
||||
|
||||
/** Output nach Submit: URL + Dateiname pro generiertem Dokument */
|
||||
generated_documents?: GeneratedDocument[]
|
||||
}
|
||||
|
||||
export interface GeneratedDocument {
|
||||
document_type: string
|
||||
title: string
|
||||
download_url: string
|
||||
size_bytes: number
|
||||
generated_at: string
|
||||
}
|
||||
|
||||
/** Default-State für einen frischen Wizard */
|
||||
export function defaultFoundingWizardState(): FoundingWizardState {
|
||||
return {
|
||||
current_step: 1,
|
||||
lifecycle_stage: 'founding',
|
||||
is_pre_notary: true,
|
||||
basics: {
|
||||
company_name: '',
|
||||
legal_form: 'GmbH',
|
||||
company_seat: '',
|
||||
company_address: '',
|
||||
company_purpose_description: '',
|
||||
company_purpose_bullets: [],
|
||||
industry: '',
|
||||
business_year: 'Kalenderjahr',
|
||||
has_research_focus: false,
|
||||
register_court: '',
|
||||
hrb_number: '',
|
||||
},
|
||||
gesellschafter: [],
|
||||
capital: {
|
||||
stammkapital_eur: 25000,
|
||||
einlage_method: 'Geld',
|
||||
einlage_quote_initial_pct: 50,
|
||||
has_sacheinlage: false,
|
||||
},
|
||||
notar: {
|
||||
notary_name: '',
|
||||
notary_place: '',
|
||||
},
|
||||
sha: {
|
||||
has_sha: true,
|
||||
vesting_months: 48,
|
||||
cliff_months: 12,
|
||||
drag_along_threshold_pct: 75,
|
||||
tag_along_threshold_pct: 20,
|
||||
reserved_matters_majority_pct: 75,
|
||||
has_beirat: false,
|
||||
has_texas_shootout: false,
|
||||
has_ceo_designation: false,
|
||||
esop_pool_pct: 0,
|
||||
},
|
||||
gf_contracts: [],
|
||||
selected_documents: [
|
||||
'articles_of_association',
|
||||
'gesellschafterliste',
|
||||
'gf_bestellungsbeschluss',
|
||||
'hrb_anmeldung',
|
||||
'sha',
|
||||
'geschaeftsordnung_gf',
|
||||
'managing_director_employment_contract',
|
||||
'ip_assignment_agreement',
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* Playwright E2E-Test: Founding-Wizard mit 2-Mann GmbH (Benjamin Bönisch + Sharang Parnerkar).
|
||||
*
|
||||
* Test-Flow:
|
||||
* 1. Lokale Dev-URL aufrufen
|
||||
* 2. Wizard durch alle 8 Steps befüllen
|
||||
* 3. Dokumente generieren (8 Stück für Notartermin-Bundle)
|
||||
* 4. Word-Download-Links validieren
|
||||
*
|
||||
* Voraussetzung: `npm run dev` läuft auf http://localhost:3007
|
||||
* Backend ist erreichbar (mit Migration 137 + 138 + Templates 123–136)
|
||||
*
|
||||
* Ausführen:
|
||||
* cd admin-compliance
|
||||
* npx playwright test tests/playwright/founding-wizard/
|
||||
*/
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
|
||||
const BASE_URL = process.env.WIZARD_URL || 'http://localhost:3007/sdk/founding-wizard'
|
||||
|
||||
const TEST_DATA = {
|
||||
basics: {
|
||||
company_name: 'Breakpilot GmbH',
|
||||
company_seat: 'Bietigheim-Bissingen',
|
||||
company_address: 'Hauptstraße 1, 74321 Bietigheim-Bissingen',
|
||||
industry: 'Software / KI / SaaS',
|
||||
purpose: 'die Entwicklung, Bereitstellung und der Vertrieb von Softwarelösungen, Plattformen und IT-Dienstleistungen im Bereich der Künstlichen Intelligenz sowie compliance-bezogener Datenverarbeitungssysteme',
|
||||
bullets: [
|
||||
'a) Entwicklung, Programmierung und Betrieb von KI-gestützter Compliance-Software',
|
||||
'b) Bereitstellung von datenschutzkonformen SaaS-Lösungen für Unternehmen',
|
||||
'c) Beratungs- und Integrationsleistungen im Compliance-Umfeld',
|
||||
],
|
||||
},
|
||||
notar: {
|
||||
name: 'Dr. Müller',
|
||||
place: 'Stuttgart',
|
||||
address: 'Königstraße 1, 70173 Stuttgart',
|
||||
date: '2026-06-15',
|
||||
},
|
||||
gesellschafter: [
|
||||
{
|
||||
name: 'Benjamin Bönisch',
|
||||
birthdate: '1980-03-15',
|
||||
address: 'Hauptstraße 1, 74321 Bietigheim-Bissingen',
|
||||
email: 'benjamin@breakpilot.ai',
|
||||
nennbetrag: 12500,
|
||||
is_gf: true,
|
||||
role: 'CEO',
|
||||
},
|
||||
{
|
||||
name: 'Sharang Parnerkar',
|
||||
birthdate: '1985-09-22',
|
||||
address: 'Hauptstraße 2, 74321 Bietigheim-Bissingen',
|
||||
email: 'sharang@breakpilot.ai',
|
||||
nennbetrag: 12500,
|
||||
is_gf: true,
|
||||
role: 'CTO',
|
||||
},
|
||||
],
|
||||
stammkapital: 25000,
|
||||
}
|
||||
|
||||
test.describe('Founding Wizard — 2-Mann GmbH', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Clear localStorage to start fresh
|
||||
await page.goto(BASE_URL)
|
||||
await page.evaluate(() => localStorage.clear())
|
||||
await page.reload()
|
||||
})
|
||||
|
||||
test('füllt komplette 2-Mann GmbH aus und generiert Notartermin-Bundle', async ({ page }) => {
|
||||
await page.goto(BASE_URL)
|
||||
await expect(page.getByTestId('founding-wizard')).toBeVisible()
|
||||
|
||||
// STEP 1: Basics
|
||||
await expect(page.getByTestId('step-content-1')).toBeVisible()
|
||||
await page.getByTestId('company-name').fill(TEST_DATA.basics.company_name)
|
||||
await page.getByTestId('legal-form').selectOption('GmbH')
|
||||
await page.getByTestId('company-seat').fill(TEST_DATA.basics.company_seat)
|
||||
await page.getByTestId('company-address').fill(TEST_DATA.basics.company_address)
|
||||
await page.getByTestId('industry').fill(TEST_DATA.basics.industry)
|
||||
await page.getByTestId('company-purpose').fill(TEST_DATA.basics.purpose)
|
||||
await page.getByTestId('company-purpose-bullets').fill(TEST_DATA.basics.bullets.join('\n'))
|
||||
await page.getByTestId('next-step').click()
|
||||
|
||||
// STEP 2: Gesellschafter
|
||||
await expect(page.getByTestId('step-content-2')).toBeVisible()
|
||||
for (const gs of TEST_DATA.gesellschafter) {
|
||||
await page.getByTestId('gs-name').fill(gs.name)
|
||||
await page.getByTestId('gs-birthdate').fill(gs.birthdate)
|
||||
await page.getByTestId('gs-address').fill(gs.address)
|
||||
await page.getByTestId('gs-email').fill(gs.email)
|
||||
await page.getByTestId('gs-nennbetrag').fill(String(gs.nennbetrag))
|
||||
await page.getByTestId('gs-role').fill(gs.role)
|
||||
// is_gf bereits default true, nichts zu tun
|
||||
await page.getByTestId('add-gesellschafter').click()
|
||||
}
|
||||
await expect(page.getByTestId('gs-row-1')).toContainText('Benjamin Bönisch')
|
||||
await expect(page.getByTestId('gs-row-2')).toContainText('Sharang Parnerkar')
|
||||
await expect(page.getByTestId('gs-total')).toContainText('25.000')
|
||||
await page.getByTestId('next-step').click()
|
||||
|
||||
// STEP 3: GF-Assignment (beide bereits GF aus Step 2)
|
||||
await expect(page.getByTestId('step-content-3')).toBeVisible()
|
||||
await page.getByTestId('next-step').click()
|
||||
|
||||
// STEP 4: Kapital
|
||||
await expect(page.getByTestId('step-content-4')).toBeVisible()
|
||||
await expect(page.getByTestId('stammkapital')).toHaveValue('25000')
|
||||
await page.getByTestId('einlage-method').selectOption('Geld')
|
||||
await page.getByTestId('einlage-quote').fill('50')
|
||||
await page.getByTestId('next-step').click()
|
||||
|
||||
// STEP 5: Notar
|
||||
await expect(page.getByTestId('step-content-5')).toBeVisible()
|
||||
await page.getByTestId('notary-name').fill(TEST_DATA.notar.name)
|
||||
await page.getByTestId('notary-place').fill(TEST_DATA.notar.place)
|
||||
await page.getByTestId('notary-address').fill(TEST_DATA.notar.address)
|
||||
await page.getByTestId('notarial-date').fill(TEST_DATA.notar.date)
|
||||
await page.getByTestId('next-step').click()
|
||||
|
||||
// STEP 6: SHA-Optionen
|
||||
await expect(page.getByTestId('step-content-6')).toBeVisible()
|
||||
await expect(page.getByTestId('has-sha')).toBeChecked()
|
||||
await expect(page.getByTestId('vesting-months')).toHaveValue('48')
|
||||
await expect(page.getByTestId('drag-along-pct')).toHaveValue('75')
|
||||
await page.getByTestId('next-step').click()
|
||||
|
||||
// STEP 7: GF-Verträge (für beide Founders)
|
||||
await expect(page.getByTestId('step-content-7')).toBeVisible()
|
||||
// GF-Contracts werden mit Defaults erzeugt sobald GFs definiert sind -
|
||||
// wir editieren die Gehälter
|
||||
const contracts = page.locator('[data-testid^="contract-"]')
|
||||
const count = await contracts.count()
|
||||
expect(count).toBe(2)
|
||||
await page.getByTestId('next-step').click()
|
||||
|
||||
// STEP 8: Generate
|
||||
await expect(page.getByTestId('step-content-8')).toBeVisible()
|
||||
await expect(page.getByTestId('generate-summary')).toContainText('Breakpilot GmbH')
|
||||
await expect(page.getByTestId('generate-summary')).toContainText('Bietigheim-Bissingen')
|
||||
await expect(page.getByTestId('generate-summary')).toContainText('25.000')
|
||||
|
||||
// Notartermin-Bundle auswählen
|
||||
await page.getByTestId('select-notary-bundle').click()
|
||||
// Check that bundle items are selected
|
||||
await expect(page.getByTestId('doc-articles_of_association')).toBeChecked()
|
||||
await expect(page.getByTestId('doc-sha')).toBeChecked()
|
||||
await expect(page.getByTestId('doc-gesellschafterliste')).toBeChecked()
|
||||
await expect(page.getByTestId('doc-managing_director_employment_contract')).toBeChecked()
|
||||
|
||||
// Generate
|
||||
await page.getByTestId('generate-docs').click()
|
||||
|
||||
// Warten auf Generierung (max 30s)
|
||||
await expect(page.getByTestId('generated-docs')).toBeVisible({ timeout: 30000 })
|
||||
|
||||
// Mindestens 8 Dokumente sollten erscheinen (für 2 Founders evtl. doppelt: GF-Vertrag, IP-Assignment)
|
||||
const downloadLinks = page.locator('[data-testid^="download-"]')
|
||||
const linkCount = await downloadLinks.count()
|
||||
expect(linkCount).toBeGreaterThanOrEqual(8)
|
||||
|
||||
// Validiere dass download-URLs data: URLs sind (base64 DOCX)
|
||||
for (let i = 0; i < Math.min(linkCount, 3); i++) {
|
||||
const href = await downloadLinks.nth(i).getAttribute('href')
|
||||
expect(href).toMatch(/^data:application\/vnd\.openxmlformats-officedocument\.wordprocessingml\.document;base64,/)
|
||||
}
|
||||
|
||||
// Screenshot fürs Test-Artifact
|
||||
await page.screenshot({ path: 'test-results/founding-wizard-final.png', fullPage: true })
|
||||
})
|
||||
|
||||
test('zeigt Validierung wenn Pflichtfelder fehlen', async ({ page }) => {
|
||||
await page.goto(BASE_URL)
|
||||
// Next-Button sollte disabled sein wenn nichts ausgefüllt
|
||||
await expect(page.getByTestId('next-step')).toBeDisabled()
|
||||
|
||||
await page.getByTestId('company-name').fill('Test')
|
||||
// Immer noch disabled weil purpose fehlt
|
||||
await expect(page.getByTestId('next-step')).toBeDisabled()
|
||||
|
||||
await page.getByTestId('company-seat').fill('Stuttgart')
|
||||
await page.getByTestId('company-purpose').fill('Eine lange genug Beschreibung des Zwecks.')
|
||||
// Jetzt sollte er enabled sein
|
||||
await expect(page.getByTestId('next-step')).toBeEnabled()
|
||||
})
|
||||
|
||||
test('Reset löscht alle Daten', async ({ page }) => {
|
||||
await page.goto(BASE_URL)
|
||||
await page.getByTestId('company-name').fill('Wird gelöscht GmbH')
|
||||
page.on('dialog', d => d.accept())
|
||||
await page.getByTestId('reset-wizard').click()
|
||||
await expect(page.getByTestId('company-name')).toHaveValue('')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,241 @@
|
||||
// Command iace-audit runs static and runtime audits on the IACE pattern
|
||||
// engine to find gaps without a ground-truth reference.
|
||||
//
|
||||
// Subcommands:
|
||||
//
|
||||
// reachability — Method A: which patterns can never fire given the library?
|
||||
// consistency — Method B: do components cover their TypicalHazardCategories?
|
||||
// vocabulary — Method C: which limits-form words are unknown to the dict?
|
||||
// echo — Method D: which limits-form sentences have no hazard echo?
|
||||
// hierarchy — Method E: which hazards lack design/protection/information?
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/breakpilot/ai-compliance-sdk/internal/iace/audit"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
switch os.Args[1] {
|
||||
case "reachability":
|
||||
cmdReachability(os.Args[2:])
|
||||
case "consistency":
|
||||
cmdConsistency(os.Args[2:])
|
||||
case "vocabulary":
|
||||
cmdVocabulary(os.Args[2:])
|
||||
case "echo":
|
||||
cmdEcho(os.Args[2:])
|
||||
case "hierarchy":
|
||||
cmdHierarchy(os.Args[2:])
|
||||
default:
|
||||
usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintln(os.Stderr, "Usage: iace-audit <reachability|consistency|vocabulary|echo|hierarchy> [args]")
|
||||
}
|
||||
|
||||
func cmdReachability(_ []string) {
|
||||
r := audit.RunReachability()
|
||||
printSummary(fmt.Sprintf("Method A — Pattern Reachability"), map[string]int{
|
||||
"total": r.TotalPatterns,
|
||||
"reachable": r.Reachable,
|
||||
"weakly_reachable": r.WeaklyReachable,
|
||||
"unreachable": r.Unreachable,
|
||||
"universe_tags": len(r.UniverseTags),
|
||||
})
|
||||
if len(r.UnreachablePatterns) > 0 {
|
||||
fmt.Println("\n## Unreachable patterns (top 30 by priority):\n")
|
||||
printPatternRows(r.UnreachablePatterns, 30)
|
||||
}
|
||||
if len(r.WeakPatterns) > 0 {
|
||||
fmt.Println("\n## Weakly reachable (top 20 by priority):\n")
|
||||
printPatternRows(r.WeakPatterns, 20)
|
||||
}
|
||||
writeJSON("audit-reports/reachability.json", r)
|
||||
}
|
||||
|
||||
func cmdConsistency(_ []string) {
|
||||
r := audit.RunConsistency()
|
||||
printSummary("Method B — Component Self-Consistency", map[string]int{
|
||||
"total_components": r.TotalComponents,
|
||||
"consistent": r.Consistent,
|
||||
"incomplete": r.Incomplete,
|
||||
})
|
||||
if len(r.IncompleteComponents) > 0 {
|
||||
fmt.Println("\n## Components missing tags for declared hazard categories:\n")
|
||||
for _, c := range r.IncompleteComponents {
|
||||
fmt.Printf("- %s (%s)\n", c.ComponentID, c.NameDE)
|
||||
for _, miss := range c.MissingForCategories {
|
||||
fmt.Printf(" %s: no pattern fires (suggest tags: %s)\n", miss.Category, joinFirst(miss.SuggestedTags, 5))
|
||||
}
|
||||
}
|
||||
}
|
||||
writeJSON("audit-reports/consistency.json", r)
|
||||
}
|
||||
|
||||
func cmdVocabulary(args []string) {
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "vocabulary: missing path to limits-form JSON")
|
||||
os.Exit(2)
|
||||
}
|
||||
data, err := os.ReadFile(args[0])
|
||||
must(err)
|
||||
var form map[string]any
|
||||
must(json.Unmarshal(data, &form))
|
||||
r := audit.RunVocabulary(form)
|
||||
printSummary("Method C — Vocabulary Diff", map[string]int{
|
||||
"unique_tokens": r.UniqueTokens,
|
||||
"unknown_tokens": len(r.UnknownTokens),
|
||||
"unknown_with_pattern_hit": len(r.SuggestedDictionaryEntries),
|
||||
})
|
||||
if len(r.SuggestedDictionaryEntries) > 0 {
|
||||
fmt.Println("\n## Suggested dictionary additions (token appears in pattern scenarios but not in dict):\n")
|
||||
for _, s := range r.SuggestedDictionaryEntries {
|
||||
fmt.Printf("- '%s' → seen in %d patterns. Examples: %s\n", s.Token, len(s.PatternIDs), joinFirst(s.PatternIDs, 5))
|
||||
}
|
||||
}
|
||||
writeJSON("audit-reports/vocabulary.json", r)
|
||||
}
|
||||
|
||||
func cmdEcho(args []string) {
|
||||
if len(args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "echo: usage: iace-audit echo <limits-form.json> <hazards.json>")
|
||||
os.Exit(2)
|
||||
}
|
||||
limitsData, err := os.ReadFile(args[0])
|
||||
must(err)
|
||||
hazardsData, err := os.ReadFile(args[1])
|
||||
must(err)
|
||||
var form map[string]any
|
||||
must(json.Unmarshal(limitsData, &form))
|
||||
var hwrap struct {
|
||||
Hazards []map[string]any `json:"hazards"`
|
||||
}
|
||||
must(json.Unmarshal(hazardsData, &hwrap))
|
||||
r := audit.RunEcho(form, hwrap.Hazards)
|
||||
printSummary("Method D — Limits-Form Echo", map[string]int{
|
||||
"total_phrases": r.TotalPhrases,
|
||||
"echoed": r.Echoed,
|
||||
"orphaned": r.Orphaned,
|
||||
})
|
||||
if len(r.OrphanedPhrases) > 0 {
|
||||
fmt.Println("\n## Orphaned phrases (no hazard echoes them):\n")
|
||||
for _, o := range r.OrphanedPhrases {
|
||||
fmt.Printf("- [%s] %s\n", o.Field, truncate(o.Phrase, 120))
|
||||
}
|
||||
}
|
||||
writeJSON("audit-reports/echo.json", r)
|
||||
}
|
||||
|
||||
func cmdHierarchy(args []string) {
|
||||
if len(args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "hierarchy: usage: iace-audit hierarchy <hazards.json> <mitigations.json>")
|
||||
os.Exit(2)
|
||||
}
|
||||
hData, err := os.ReadFile(args[0])
|
||||
must(err)
|
||||
mData, err := os.ReadFile(args[1])
|
||||
must(err)
|
||||
var hwrap struct {
|
||||
Hazards []map[string]any `json:"hazards"`
|
||||
}
|
||||
must(json.Unmarshal(hData, &hwrap))
|
||||
var mwrap struct {
|
||||
Mitigations []map[string]any `json:"mitigations"`
|
||||
}
|
||||
must(json.Unmarshal(mData, &mwrap))
|
||||
r := audit.RunHierarchy(hwrap.Hazards, mwrap.Mitigations)
|
||||
printSummary("Method E — Hierarchy Completeness", map[string]int{
|
||||
"total_hazards": r.TotalHazards,
|
||||
"complete": r.Complete,
|
||||
"missing_design": r.MissingDesign,
|
||||
"missing_protection": r.MissingProtection,
|
||||
"missing_info": r.MissingInfo,
|
||||
})
|
||||
if len(r.IncompleteHazards) > 0 {
|
||||
fmt.Println("\n## Hazards with incomplete hierarchy:\n")
|
||||
for _, h := range r.IncompleteHazards {
|
||||
fmt.Printf("- [%s] %s — missing: %s\n", h.Category, truncate(h.Name, 70), joinFirst(h.MissingLevels, 3))
|
||||
}
|
||||
}
|
||||
writeJSON("audit-reports/hierarchy.json", r)
|
||||
}
|
||||
|
||||
func printSummary(title string, kv map[string]int) {
|
||||
fmt.Println("=", title, "=")
|
||||
for k, v := range kv {
|
||||
fmt.Printf(" %-22s %d\n", k, v)
|
||||
}
|
||||
}
|
||||
|
||||
func printPatternRows(rows []audit.ReachabilityResult, max int) {
|
||||
if max > len(rows) {
|
||||
max = len(rows)
|
||||
}
|
||||
for i := 0; i < max; i++ {
|
||||
r := rows[i]
|
||||
fmt.Printf("- %s (P%d) %s\n", r.PatternID, r.Priority, truncate(r.Name, 60))
|
||||
if len(r.UnreachableTags) > 0 {
|
||||
fmt.Printf(" missing tags: %s\n", joinFirst(r.UnreachableTags, 8))
|
||||
}
|
||||
for _, s := range r.FixSuggestions {
|
||||
fmt.Printf(" fix: %s\n", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeJSON(path string, v any) {
|
||||
_ = os.MkdirAll("audit-reports", 0o755)
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "warn: could not write report:", err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
enc := json.NewEncoder(f)
|
||||
enc.SetIndent("", " ")
|
||||
_ = enc.Encode(v)
|
||||
fmt.Println("→ wrote", path)
|
||||
}
|
||||
|
||||
func must(err error) {
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func truncate(s string, n int) string {
|
||||
if len(s) <= n {
|
||||
return s
|
||||
}
|
||||
return s[:n] + "…"
|
||||
}
|
||||
|
||||
func joinFirst(list []string, n int) string {
|
||||
if len(list) <= n {
|
||||
return join(list)
|
||||
}
|
||||
return join(list[:n]) + ", …"
|
||||
}
|
||||
|
||||
func join(list []string) string {
|
||||
out := ""
|
||||
for i, s := range list {
|
||||
if i > 0 {
|
||||
out += ", "
|
||||
}
|
||||
out += s
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package handlers
|
||||
|
||||
// LLM Gap-Review handler — Task #7.
|
||||
//
|
||||
// After the deterministic Pattern-Engine has generated hazards and
|
||||
// mitigations for an IACE project, this endpoint asks a configured LLM
|
||||
// (Qwen / Claude / OpenAI) to spot what the engine MISSED. The LLM is
|
||||
// fed the Limits-Form, the current hazard list, and a compressed
|
||||
// pattern catalogue summary; it returns a list of suggested additional
|
||||
// hazards or mitigations.
|
||||
//
|
||||
// Important guardrails:
|
||||
// - Every suggestion must point to an existing pattern_id or norm
|
||||
// identifier — pure free-form LLM hallucinations are filtered.
|
||||
// - The response is provenance-tagged source="llm_gap_review" so
|
||||
// the frontend renders an Adopt/Reject UX rather than committing.
|
||||
// - Engine output (deterministic patterns) is never overwritten by
|
||||
// LLM output; the gap-review is a SUPPLEMENT, not a replacement.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/breakpilot/ai-compliance-sdk/internal/iace"
|
||||
"github.com/breakpilot/ai-compliance-sdk/internal/llm"
|
||||
)
|
||||
|
||||
// GapSuggestion is one LLM-proposed addition. Each suggestion is
|
||||
// non-binding until the user adopts it via the frontend.
|
||||
type GapSuggestion struct {
|
||||
Kind string `json:"kind"` // "hazard" | "mitigation"
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Category string `json:"category,omitempty"`
|
||||
HazardRef string `json:"hazard_ref,omitempty"` // for mitigation: name of existing hazard
|
||||
PatternRef string `json:"pattern_ref,omitempty"` // HP-XXXX from engine library
|
||||
NormRefs []string `json:"norm_refs,omitempty"` // EN ISO 12100 / DGUV / OSHA
|
||||
Confidence string `json:"confidence,omitempty"` // "high" | "medium" | "low"
|
||||
Rationale string `json:"rationale,omitempty"`
|
||||
}
|
||||
|
||||
// GapReviewResponse is the wire format for the frontend modal.
|
||||
type GapReviewResponse struct {
|
||||
ProjectID string `json:"project_id"`
|
||||
Source string `json:"source"` // "llm_gap_review" | "fallback_static"
|
||||
Model string `json:"model,omitempty"`
|
||||
Suggestions []GapSuggestion `json:"suggestions"`
|
||||
InputSummary struct {
|
||||
HazardCount int `json:"hazard_count"`
|
||||
MitigationCount int `json:"mitigation_count"`
|
||||
LimitsFormFields int `json:"limits_form_fields"`
|
||||
} `json:"input_summary"`
|
||||
}
|
||||
|
||||
// LLMGapReview handles POST /projects/:id/llm-gap-review.
|
||||
//
|
||||
// The endpoint is intentionally idempotent — repeated calls do not mutate
|
||||
// project state. The Adopt step (user-driven) is what changes data, via
|
||||
// the existing CreateHazard / CreateMitigation handlers.
|
||||
func (h *IACEHandler) LLMGapReview(c *gin.Context) {
|
||||
projectID, err := uuid.Parse(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project id"})
|
||||
return
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
project, err := h.store.GetProject(ctx, projectID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "project not found"})
|
||||
return
|
||||
}
|
||||
|
||||
hazards, err := h.store.ListHazards(ctx, projectID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "list hazards: " + err.Error()})
|
||||
return
|
||||
}
|
||||
mitigations, err := h.store.ListMitigationsByProject(ctx, projectID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "list mitigations: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
limitsForm := extractLimitsForm(project)
|
||||
prompt := buildGapReviewPrompt(project, hazards, mitigations, limitsForm)
|
||||
|
||||
resp := GapReviewResponse{ProjectID: projectID.String()}
|
||||
resp.InputSummary.HazardCount = len(hazards)
|
||||
resp.InputSummary.MitigationCount = len(mitigations)
|
||||
resp.InputSummary.LimitsFormFields = countLimitsFields(limitsForm)
|
||||
|
||||
suggestions, model, err := callLLMForGapReview(ctx, h.llmRegistry, prompt)
|
||||
if err != nil {
|
||||
resp.Source = "fallback_static"
|
||||
resp.Suggestions = staticFallbackSuggestions(hazards)
|
||||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
|
||||
resp.Source = "llm_gap_review"
|
||||
resp.Model = model
|
||||
resp.Suggestions = filterAndProvenance(suggestions)
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// extractLimitsForm pulls the structured limits-form out of project metadata.
|
||||
func extractLimitsForm(p *iace.Project) map[string]any {
|
||||
if len(p.Metadata) == 0 {
|
||||
return nil
|
||||
}
|
||||
var md map[string]any
|
||||
if err := json.Unmarshal(p.Metadata, &md); err != nil {
|
||||
return nil
|
||||
}
|
||||
lf, _ := md["limits_form"].(map[string]any)
|
||||
return lf
|
||||
}
|
||||
|
||||
func countLimitsFields(lf map[string]any) int {
|
||||
n := 0
|
||||
for _, v := range lf {
|
||||
if s, ok := v.(string); ok && strings.TrimSpace(s) != "" {
|
||||
n++
|
||||
} else if arr, ok := v.([]any); ok && len(arr) > 0 {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// buildGapReviewPrompt assembles the LLM input. Kept compact — the LLM
|
||||
// only needs the limits-form context, the current hazard headlines, and
|
||||
// a reminder of the pattern-id naming so its suggestions can be linked
|
||||
// back to engine output later.
|
||||
func buildGapReviewPrompt(p *iace.Project, hz []iace.Hazard, mt []iace.Mitigation, lf map[string]any) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("Du bist CE-Sicherheitsexperte fuer Maschinen nach EN ISO 12100. ")
|
||||
sb.WriteString("Analysiere die folgende Risikobeurteilung und identifiziere FEHLENDE ")
|
||||
sb.WriteString("Gefaehrdungen oder Schutzmassnahmen, die ein erfahrener Auditor ergaenzen wuerde.\n\n")
|
||||
|
||||
sb.WriteString(fmt.Sprintf("Maschine: %s (Typ: %s, Hersteller: %s)\n",
|
||||
p.MachineName, p.MachineType, p.Manufacturer))
|
||||
if p.CEMarkingTarget != "" {
|
||||
sb.WriteString(fmt.Sprintf("CE-Ziel: %s\n", p.CEMarkingTarget))
|
||||
}
|
||||
sb.WriteString("\nGrenzen-Form (Limits & Verwendung):\n")
|
||||
for k, v := range lf {
|
||||
sb.WriteString(fmt.Sprintf("- %s: %v\n", k, truncForPrompt(v, 200)))
|
||||
}
|
||||
|
||||
sb.WriteString(fmt.Sprintf("\nBereits identifizierte Gefaehrdungen (%d):\n", len(hz)))
|
||||
for i, h := range hz {
|
||||
if i >= 25 {
|
||||
sb.WriteString(fmt.Sprintf("... und %d weitere\n", len(hz)-25))
|
||||
break
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("- [%s] %s\n", h.Category, h.Name))
|
||||
}
|
||||
|
||||
sb.WriteString(fmt.Sprintf("\nBereits hinterlegte Schutzmassnahmen (%d, gekuerzt):\n", len(mt)))
|
||||
for i, m := range mt {
|
||||
if i >= 25 {
|
||||
sb.WriteString(fmt.Sprintf("... und %d weitere\n", len(mt)-25))
|
||||
break
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("- [%s] %s\n", m.ReductionType, m.Name))
|
||||
}
|
||||
|
||||
sb.WriteString("\nAufgabe: Liste max. 8 LUECKEN als JSON-Array. Jede Luecke MUSS einer der folgenden Kategorien entsprechen ")
|
||||
sb.WriteString("und SOLL eine Norm- oder Pattern-Referenz nennen (HP-XXXX, EN ISO 12100, EN 13849, EN 13855, DGUV-Info, OSHA 29 CFR).\n")
|
||||
sb.WriteString("Kategorien: mechanical_hazard, electrical_hazard, thermal_hazard, noise_vibration, ergonomic, ")
|
||||
sb.WriteString("material_environmental, pneumatic_hydraulic, radiation_hazard.\n\n")
|
||||
sb.WriteString(`Antworte NUR mit JSON, keine Erklaerung:
|
||||
[
|
||||
{"kind":"hazard","title":"...","description":"...","category":"...","norm_refs":["EN ISO 12100"],"confidence":"high","rationale":"..."},
|
||||
{"kind":"mitigation","title":"...","description":"...","hazard_ref":"Name der bestehenden Gefahr","norm_refs":["DGUV 209-072"],"confidence":"medium","rationale":"..."}
|
||||
]`)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func truncForPrompt(v any, max int) string {
|
||||
s := fmt.Sprintf("%v", v)
|
||||
if len(s) <= max {
|
||||
return s
|
||||
}
|
||||
return s[:max] + "…"
|
||||
}
|
||||
|
||||
// callLLMForGapReview sends the prompt and parses the JSON suggestion list.
|
||||
func callLLMForGapReview(ctx context.Context, registry *llm.ProviderRegistry, prompt string) ([]GapSuggestion, string, error) {
|
||||
if registry == nil {
|
||||
return nil, "", fmt.Errorf("no LLM registry configured")
|
||||
}
|
||||
provider, err := registry.GetAvailable(ctx)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("no LLM provider available: %w", err)
|
||||
}
|
||||
resp, err := provider.Chat(ctx, &llm.ChatRequest{
|
||||
Messages: []llm.Message{{Role: "user", Content: prompt}},
|
||||
Temperature: 0.25,
|
||||
MaxTokens: 2000,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("llm chat: %w", err)
|
||||
}
|
||||
|
||||
body := strings.TrimSpace(resp.Message.Content)
|
||||
// LLMs occasionally wrap JSON in ```json … ``` fences; strip them.
|
||||
body = strings.TrimPrefix(body, "```json")
|
||||
body = strings.TrimPrefix(body, "```")
|
||||
body = strings.TrimSuffix(body, "```")
|
||||
body = strings.TrimSpace(body)
|
||||
|
||||
// Find first '[' so any leading prose is ignored.
|
||||
if i := strings.Index(body, "["); i > 0 {
|
||||
body = body[i:]
|
||||
}
|
||||
var out []GapSuggestion
|
||||
if err := json.Unmarshal([]byte(body), &out); err != nil {
|
||||
return nil, "", fmt.Errorf("parse llm response: %w (body=%.200s)", err, body)
|
||||
}
|
||||
return out, provider.Name(), nil
|
||||
}
|
||||
|
||||
// filterAndProvenance drops obviously malformed suggestions and stamps
|
||||
// every survivor with a `confidence` default. Pure-free-form suggestions
|
||||
// without any norm reference are demoted to "low".
|
||||
func filterAndProvenance(in []GapSuggestion) []GapSuggestion {
|
||||
out := make([]GapSuggestion, 0, len(in))
|
||||
for _, s := range in {
|
||||
if strings.TrimSpace(s.Title) == "" || s.Kind == "" {
|
||||
continue
|
||||
}
|
||||
if s.Confidence == "" {
|
||||
if len(s.NormRefs) == 0 && s.PatternRef == "" {
|
||||
s.Confidence = "low"
|
||||
} else {
|
||||
s.Confidence = "medium"
|
||||
}
|
||||
}
|
||||
out = append(out, s)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// staticFallbackSuggestions returns a generic checklist when no LLM is
|
||||
// available. Conservative, all confidence="low".
|
||||
func staticFallbackSuggestions(hz []iace.Hazard) []GapSuggestion {
|
||||
hasMechanical := false
|
||||
for _, h := range hz {
|
||||
if strings.Contains(h.Category, "mechanical") {
|
||||
hasMechanical = true
|
||||
break
|
||||
}
|
||||
}
|
||||
out := []GapSuggestion{
|
||||
{
|
||||
Kind: "hazard", Title: "Fuss-Quetschung unter absenkendem Werkstueck/Hubeinheit",
|
||||
Description: "Wenn die Maschine eine Hubbewegung ausfuehrt, pruefe ob Fuesse/Beine im Verfahrbereich gequetscht werden koennen.",
|
||||
Category: "mechanical_hazard", NormRefs: []string{"EN ISO 12100 6.3.5.5"},
|
||||
Confidence: "low", Rationale: "Static checklist fallback — LLM nicht verfuegbar.",
|
||||
},
|
||||
{
|
||||
Kind: "hazard", Title: "Hand-Quetschung gegen feste Strukturen beim Hochfahren",
|
||||
Description: "Pruefe Mindestabstand zu festen Strukturen oberhalb der hoechsten Hubposition.",
|
||||
Category: "mechanical_hazard", NormRefs: []string{"EN ISO 13854"},
|
||||
Confidence: "low",
|
||||
},
|
||||
{
|
||||
Kind: "mitigation", Title: "Kriechgeschwindigkeit am Endanschlag (Hubgeraete)",
|
||||
Description: "Hubgeschwindigkeit am Ende der Verfahrbewegung auf <=15 mm/s reduzieren.",
|
||||
NormRefs: []string{"OSHA 29 CFR 1910.217 (Hand-Speed-Konstante)"},
|
||||
Confidence: "low",
|
||||
},
|
||||
}
|
||||
if !hasMechanical {
|
||||
// Trim if not a mechanical context
|
||||
out = out[:1]
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -46,6 +46,8 @@ func (h *IACEHandler) ListNormsLibrary(c *gin.Context) {
|
||||
allNorms = append(allNorms, iace.GetWave3dHvacCNorms()...)
|
||||
allNorms = append(allNorms, iace.GetFinalCNorms()...)
|
||||
|
||||
includeCrossRef := c.Query("include_crossref") == "true"
|
||||
|
||||
var filtered []iace.NormReference
|
||||
for _, norm := range allNorms {
|
||||
if normType != "" && norm.NormType != normType {
|
||||
@@ -54,6 +56,12 @@ func (h *IACEHandler) ListNormsLibrary(c *gin.Context) {
|
||||
if hazardCat != "" && !containsString(norm.HazardCats, hazardCat) {
|
||||
continue
|
||||
}
|
||||
if includeCrossRef {
|
||||
cr := iace.GetNormCrossRef(norm.ID)
|
||||
if len(cr.Mappings) > 0 {
|
||||
norm.CrossRef = &cr
|
||||
}
|
||||
}
|
||||
filtered = append(filtered, norm)
|
||||
}
|
||||
|
||||
@@ -61,9 +69,36 @@ func (h *IACEHandler) ListNormsLibrary(c *gin.Context) {
|
||||
filtered = []iace.NormReference{}
|
||||
}
|
||||
|
||||
covered, total := iace.CrossRefCoverage(len(allNorms))
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"norms": filtered,
|
||||
"total": len(filtered),
|
||||
"crossref_coverage": gin.H{
|
||||
"covered": covered,
|
||||
"total_norms": total,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetNormCrossRef handles GET /norms-library/:id/crossref
|
||||
// Returns the international cross-reference (DIN/ANSI/GB/JIS/...) for a single norm.
|
||||
func (h *IACEHandler) GetNormCrossRef(c *gin.Context) {
|
||||
normID := c.Param("id")
|
||||
if normID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "norm id required"})
|
||||
return
|
||||
}
|
||||
cr := iace.GetNormCrossRef(normID)
|
||||
c.JSON(http.StatusOK, cr)
|
||||
}
|
||||
|
||||
// ListNormCrossRefs handles GET /norms-library/crossref
|
||||
// Returns the entire cross-reference matrix (all populated entries).
|
||||
func (h *IACEHandler) ListNormCrossRefs(c *gin.Context) {
|
||||
entries := iace.ListNormCrossRefs()
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"entries": entries,
|
||||
"total": len(entries),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Contract tests for the new /norms-library/crossref endpoints.
|
||||
// These are the practical equivalent of an OpenAPI snapshot: they pin
|
||||
// the response shape so a downstream consumer (admin-compliance,
|
||||
// developer-portal, SDK) cannot be silently broken.
|
||||
|
||||
func TestGetNormCrossRef_KnownID_ReturnsExpectedShape(t *testing.T) {
|
||||
handler := &IACEHandler{}
|
||||
w, c := newTestContext("GET", "/norms-library/ISO-12100/crossref", nil, nil, gin.Params{
|
||||
{Key: "id", Value: "ISO-12100"},
|
||||
})
|
||||
|
||||
handler.GetNormCrossRef(c)
|
||||
|
||||
if w.Code != 200 {
|
||||
t.Fatalf("expected 200, got %d body=%s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp struct {
|
||||
NormID string `json:"norm_id"`
|
||||
Mappings []struct {
|
||||
Region string `json:"region"`
|
||||
Identifier string `json:"identifier"`
|
||||
Relation string `json:"relation"`
|
||||
Confidence string `json:"confidence"`
|
||||
} `json:"mappings"`
|
||||
BatchID string `json:"batch_id"`
|
||||
}
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("response not parsable: %v body=%s", err, w.Body.String())
|
||||
}
|
||||
if resp.NormID != "ISO-12100" {
|
||||
t.Errorf("expected norm_id ISO-12100, got %q", resp.NormID)
|
||||
}
|
||||
if len(resp.Mappings) < 3 {
|
||||
t.Errorf("expected ISO-12100 to have at least 3 mappings, got %d", len(resp.Mappings))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNormCrossRef_MissingID_Returns400(t *testing.T) {
|
||||
handler := &IACEHandler{}
|
||||
w, c := newTestContext("GET", "/norms-library//crossref", nil, nil, gin.Params{
|
||||
{Key: "id", Value: ""},
|
||||
})
|
||||
|
||||
handler.GetNormCrossRef(c)
|
||||
if w.Code != 400 {
|
||||
t.Errorf("expected 400 for missing id, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNormCrossRef_UnknownID_ReturnsEmptyMappings(t *testing.T) {
|
||||
handler := &IACEHandler{}
|
||||
w, c := newTestContext("GET", "/norms-library/ISO-DOESNOTEXIST/crossref", nil, nil, gin.Params{
|
||||
{Key: "id", Value: "ISO-DOESNOTEXIST"},
|
||||
})
|
||||
|
||||
handler.GetNormCrossRef(c)
|
||||
|
||||
if w.Code != 200 {
|
||||
t.Fatalf("expected 200 for unknown id (returns empty), got %d", w.Code)
|
||||
}
|
||||
var resp struct {
|
||||
NormID string `json:"norm_id"`
|
||||
Mappings []interface{} `json:"mappings"`
|
||||
}
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("response not parsable: %v", err)
|
||||
}
|
||||
if resp.NormID != "ISO-DOESNOTEXIST" {
|
||||
t.Errorf("expected norm_id to echo back, got %q", resp.NormID)
|
||||
}
|
||||
if len(resp.Mappings) != 0 {
|
||||
t.Errorf("expected empty mappings, got %d", len(resp.Mappings))
|
||||
}
|
||||
}
|
||||
|
||||
func TestListNormCrossRefs_ReturnsAll(t *testing.T) {
|
||||
handler := &IACEHandler{}
|
||||
w, c := newTestContext("GET", "/norms-library/crossref", nil, nil, nil)
|
||||
|
||||
handler.ListNormCrossRefs(c)
|
||||
|
||||
if w.Code != 200 {
|
||||
t.Fatalf("expected 200, got %d", w.Code)
|
||||
}
|
||||
var resp struct {
|
||||
Entries []struct {
|
||||
NormID string `json:"norm_id"`
|
||||
} `json:"entries"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("response not parsable: %v", err)
|
||||
}
|
||||
if resp.Total != 671 {
|
||||
t.Errorf("expected 671 cross-ref entries, got %d", resp.Total)
|
||||
}
|
||||
if len(resp.Entries) != resp.Total {
|
||||
t.Errorf("entries count %d does not match total %d", len(resp.Entries), resp.Total)
|
||||
}
|
||||
}
|
||||
@@ -355,117 +355,6 @@ func registerWhistleblowerRoutes(v1 *gin.RouterGroup, h *handlers.WhistleblowerH
|
||||
}
|
||||
}
|
||||
|
||||
func registerIACERoutes(v1 *gin.RouterGroup, h *handlers.IACEHandler) {
|
||||
iaceRoutes := v1.Group("/iace")
|
||||
{
|
||||
iaceRoutes.GET("/hazard-library", h.ListHazardLibrary)
|
||||
iaceRoutes.GET("/controls-library", h.ListControlsLibrary)
|
||||
iaceRoutes.GET("/norms-library", h.ListNormsLibrary)
|
||||
iaceRoutes.GET("/lifecycle-phases", h.ListLifecyclePhases)
|
||||
iaceRoutes.GET("/roles", h.ListRoles)
|
||||
iaceRoutes.GET("/evidence-types", h.ListEvidenceTypes)
|
||||
iaceRoutes.GET("/protective-measures-library", h.ListProtectiveMeasures)
|
||||
iaceRoutes.GET("/failure-modes", h.ListFailureModes)
|
||||
iaceRoutes.GET("/operational-states", h.ListOperationalStates)
|
||||
iaceRoutes.GET("/component-library", h.ListComponentLibrary)
|
||||
iaceRoutes.GET("/energy-sources", h.ListEnergySources)
|
||||
iaceRoutes.GET("/tags", h.ListTags)
|
||||
iaceRoutes.GET("/hazard-patterns", h.ListHazardPatterns)
|
||||
iaceRoutes.POST("/projects", h.CreateProject)
|
||||
iaceRoutes.GET("/projects", h.ListProjects)
|
||||
iaceRoutes.GET("/projects/:id", h.GetProject)
|
||||
iaceRoutes.PUT("/projects/:id", h.UpdateProject)
|
||||
iaceRoutes.DELETE("/projects/:id", h.ArchiveProject)
|
||||
iaceRoutes.POST("/projects/:id/init-from-profile", h.InitFromProfile)
|
||||
iaceRoutes.POST("/projects/:id/variants", h.CreateVariant)
|
||||
iaceRoutes.GET("/projects/:id/variants", h.ListVariants)
|
||||
iaceRoutes.GET("/projects/:id/variant-gap", h.GetVariantGap)
|
||||
iaceRoutes.POST("/projects/:id/completeness-check", h.CheckCompleteness)
|
||||
iaceRoutes.POST("/projects/:id/components", h.CreateComponent)
|
||||
iaceRoutes.GET("/projects/:id/components", h.ListComponents)
|
||||
iaceRoutes.PUT("/projects/:id/components/:cid", h.UpdateComponent)
|
||||
iaceRoutes.DELETE("/projects/:id/components/:cid", h.DeleteComponent)
|
||||
iaceRoutes.POST("/projects/:id/classify", h.Classify)
|
||||
iaceRoutes.GET("/projects/:id/classifications", h.GetClassifications)
|
||||
iaceRoutes.POST("/projects/:id/classify/:regulation", h.ClassifySingle)
|
||||
iaceRoutes.POST("/projects/:id/hazards", h.CreateHazard)
|
||||
iaceRoutes.GET("/projects/:id/hazards", h.ListHazards)
|
||||
iaceRoutes.PUT("/projects/:id/hazards/:hid", h.UpdateHazard)
|
||||
iaceRoutes.POST("/projects/:id/hazards/suggest", h.SuggestHazards)
|
||||
iaceRoutes.POST("/projects/:id/match-patterns", h.MatchPatterns)
|
||||
iaceRoutes.POST("/projects/:id/parse-narrative", h.ParseNarrative)
|
||||
iaceRoutes.POST("/projects/:id/delta-analysis", h.DeltaAnalysis)
|
||||
iaceRoutes.GET("/projects/:id/fmea/export", h.ExportFMEA)
|
||||
iaceRoutes.POST("/projects/:id/components/:cid/suggest-fms", h.SuggestFailureModes)
|
||||
iaceRoutes.POST("/projects/:id/apply-patterns", h.ApplyPatternResults)
|
||||
iaceRoutes.POST("/projects/:id/hazards/:hid/suggest-measures", h.SuggestMeasuresForHazard)
|
||||
iaceRoutes.POST("/projects/:id/mitigations/:mid/suggest-evidence", h.SuggestEvidenceForMitigation)
|
||||
iaceRoutes.POST("/projects/:id/hazards/:hid/assess", h.AssessRisk)
|
||||
iaceRoutes.GET("/projects/:id/risk-summary", h.GetRiskSummary)
|
||||
iaceRoutes.GET("/projects/:id/suggested-norms", h.SuggestProjectNorms)
|
||||
iaceRoutes.POST("/projects/:id/hazards/:hid/reassess", h.ReassessRisk)
|
||||
iaceRoutes.GET("/projects/:id/mitigations", h.ListProjectMitigations)
|
||||
iaceRoutes.POST("/projects/:id/hazards/:hid/mitigations", h.CreateMitigation)
|
||||
iaceRoutes.DELETE("/projects/:id/mitigations/:mid", h.DeleteMitigation)
|
||||
iaceRoutes.PUT("/mitigations/:mid", h.UpdateMitigation)
|
||||
iaceRoutes.POST("/mitigations/:mid/verify", h.VerifyMitigation)
|
||||
iaceRoutes.POST("/projects/:id/validate-mitigation-hierarchy", h.ValidateMitigationHierarchy)
|
||||
iaceRoutes.POST("/projects/:id/evidence", h.UploadEvidence)
|
||||
iaceRoutes.GET("/projects/:id/evidence", h.ListEvidence)
|
||||
iaceRoutes.POST("/projects/:id/verification-plan", h.CreateVerificationPlan)
|
||||
iaceRoutes.PUT("/verification-plan/:vid", h.UpdateVerificationPlan)
|
||||
iaceRoutes.POST("/verification-plan/:vid/complete", h.CompleteVerification)
|
||||
iaceRoutes.GET("/projects/:id/verifications", h.ListVerificationPlans)
|
||||
iaceRoutes.POST("/projects/:id/verifications", h.CreateVerificationAlias)
|
||||
iaceRoutes.DELETE("/projects/:id/verifications/:vid", h.DeleteVerificationPlan)
|
||||
iaceRoutes.POST("/projects/:id/verifications/:vid/complete", h.CompleteVerificationAlias)
|
||||
iaceRoutes.POST("/projects/:id/tech-file/generate", h.GenerateTechFile)
|
||||
iaceRoutes.GET("/projects/:id/tech-file", h.ListTechFileSections)
|
||||
iaceRoutes.PUT("/projects/:id/tech-file/:section", h.UpdateTechFileSection)
|
||||
iaceRoutes.POST("/projects/:id/tech-file/:section/approve", h.ApproveTechFileSection)
|
||||
iaceRoutes.POST("/projects/:id/tech-file/:section/generate", h.GenerateSingleSection)
|
||||
iaceRoutes.GET("/projects/:id/tech-file/export", h.ExportTechFile)
|
||||
iaceRoutes.POST("/projects/:id/monitoring", h.CreateMonitoringEvent)
|
||||
iaceRoutes.GET("/projects/:id/monitoring", h.ListMonitoringEvents)
|
||||
iaceRoutes.PUT("/projects/:id/monitoring/:eid", h.UpdateMonitoringEvent)
|
||||
iaceRoutes.GET("/projects/:id/audit-trail", h.GetAuditTrail)
|
||||
iaceRoutes.POST("/library-search", h.SearchLibrary)
|
||||
iaceRoutes.GET("/ce-corpus-documents", h.ListCECorpusDocuments)
|
||||
iaceRoutes.POST("/projects/:id/initialize", h.InitializeProject)
|
||||
iaceRoutes.GET("/projects/:id/hazard-blocks", h.GetHazardBlocks)
|
||||
iaceRoutes.POST("/projects/:id/benchmark/import-gt", h.ImportGroundTruth)
|
||||
iaceRoutes.GET("/projects/:id/benchmark", h.RunBenchmark)
|
||||
iaceRoutes.GET("/projects/:id/benchmark/summary", h.GetBenchmarkSummary)
|
||||
iaceRoutes.GET("/projects/:id/hazards/:hid/regulatory-hints", h.EnrichHazardWithRegulations)
|
||||
iaceRoutes.GET("/projects/:id/mitigations/:mid/regulatory-hints", h.EnrichMitigationWithRegulations)
|
||||
iaceRoutes.GET("/projects/:id/regulatory-hints", h.EnrichProjectHazardsBatch)
|
||||
iaceRoutes.POST("/projects/:id/tech-file/:section/enrich", h.EnrichTechFileSection)
|
||||
|
||||
// Production Lines
|
||||
iaceRoutes.POST("/production-lines", h.CreateProductionLine)
|
||||
iaceRoutes.GET("/production-lines", h.ListProductionLines)
|
||||
iaceRoutes.GET("/production-lines/:lid/dashboard", h.GetProductionLineDashboard)
|
||||
iaceRoutes.POST("/production-lines/:lid/stations", h.AddStationToLine)
|
||||
iaceRoutes.DELETE("/production-lines/:lid/stations/:sid", h.RemoveStationFromLine)
|
||||
|
||||
// CE x Compliance Crossover
|
||||
iaceRoutes.GET("/projects/:id/compliance-triggers", h.GetComplianceTriggers)
|
||||
iaceRoutes.GET("/compliance-faq", h.GetComplianceFAQ)
|
||||
|
||||
// Clarifications — aggregated open questions per project
|
||||
iaceRoutes.GET("/projects/:id/clarifications", h.ListClarifications)
|
||||
iaceRoutes.GET("/projects/:id/clarifications.csv", h.ExportClarificationsCSV)
|
||||
iaceRoutes.GET("/projects/:id/clarifications.html", h.ExportClarificationsHTML)
|
||||
iaceRoutes.GET("/projects/:id/clarifications/:cid/detail", h.ListClarificationDetail)
|
||||
iaceRoutes.POST("/projects/:id/clarifications/:cid/answer", h.AnswerClarification)
|
||||
iaceRoutes.POST("/projects/:id/clarifications/:cid/comment", h.PostClarificationComment)
|
||||
|
||||
// Customer-Standard Reuse (migration 031): pull reusable mitigations
|
||||
// across prior projects of the same customer.
|
||||
iaceRoutes.GET("/projects/:id/customer-standards", h.ListCustomerStandardSuggestions)
|
||||
iaceRoutes.POST("/projects/:id/customer-standards/import", h.ImportCustomerStandardSuggestion)
|
||||
}
|
||||
}
|
||||
|
||||
func registerMaximizerRoutes(v1 *gin.RouterGroup, h *handlers.MaximizerHandlers) {
|
||||
m := v1.Group("/maximizer")
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
package app
|
||||
|
||||
// IACE route registration extracted from routes.go (2026-05-21) because
|
||||
// routes.go hit the 500-LOC hard cap when the LLM gap-review endpoint
|
||||
// (Task #7) was added. Splitting keeps every routes file under the cap
|
||||
// without changing behaviour — `registerRoutes` in routes.go still
|
||||
// invokes `registerIACERoutes` exactly once at the same point in the
|
||||
// startup sequence.
|
||||
|
||||
import (
|
||||
"github.com/breakpilot/ai-compliance-sdk/internal/api/handlers"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func registerIACERoutes(v1 *gin.RouterGroup, h *handlers.IACEHandler) {
|
||||
iaceRoutes := v1.Group("/iace")
|
||||
{
|
||||
// Library catalogues (read-only reference data).
|
||||
iaceRoutes.GET("/hazard-library", h.ListHazardLibrary)
|
||||
iaceRoutes.GET("/controls-library", h.ListControlsLibrary)
|
||||
iaceRoutes.GET("/norms-library", h.ListNormsLibrary)
|
||||
iaceRoutes.GET("/norms-library/crossref", h.ListNormCrossRefs)
|
||||
iaceRoutes.GET("/norms-library/:id/crossref", h.GetNormCrossRef)
|
||||
iaceRoutes.GET("/lifecycle-phases", h.ListLifecyclePhases)
|
||||
iaceRoutes.GET("/roles", h.ListRoles)
|
||||
iaceRoutes.GET("/evidence-types", h.ListEvidenceTypes)
|
||||
iaceRoutes.GET("/protective-measures-library", h.ListProtectiveMeasures)
|
||||
iaceRoutes.GET("/failure-modes", h.ListFailureModes)
|
||||
iaceRoutes.GET("/operational-states", h.ListOperationalStates)
|
||||
iaceRoutes.GET("/component-library", h.ListComponentLibrary)
|
||||
iaceRoutes.GET("/energy-sources", h.ListEnergySources)
|
||||
iaceRoutes.GET("/tags", h.ListTags)
|
||||
iaceRoutes.GET("/hazard-patterns", h.ListHazardPatterns)
|
||||
|
||||
// Project CRUD.
|
||||
iaceRoutes.POST("/projects", h.CreateProject)
|
||||
iaceRoutes.GET("/projects", h.ListProjects)
|
||||
iaceRoutes.GET("/projects/:id", h.GetProject)
|
||||
iaceRoutes.PUT("/projects/:id", h.UpdateProject)
|
||||
iaceRoutes.DELETE("/projects/:id", h.ArchiveProject)
|
||||
iaceRoutes.POST("/projects/:id/init-from-profile", h.InitFromProfile)
|
||||
iaceRoutes.POST("/projects/:id/variants", h.CreateVariant)
|
||||
iaceRoutes.GET("/projects/:id/variants", h.ListVariants)
|
||||
iaceRoutes.GET("/projects/:id/variant-gap", h.GetVariantGap)
|
||||
iaceRoutes.POST("/projects/:id/completeness-check", h.CheckCompleteness)
|
||||
|
||||
// Components.
|
||||
iaceRoutes.POST("/projects/:id/components", h.CreateComponent)
|
||||
iaceRoutes.GET("/projects/:id/components", h.ListComponents)
|
||||
iaceRoutes.PUT("/projects/:id/components/:cid", h.UpdateComponent)
|
||||
iaceRoutes.DELETE("/projects/:id/components/:cid", h.DeleteComponent)
|
||||
|
||||
// Classification + hazards.
|
||||
iaceRoutes.POST("/projects/:id/classify", h.Classify)
|
||||
iaceRoutes.GET("/projects/:id/classifications", h.GetClassifications)
|
||||
iaceRoutes.POST("/projects/:id/classify/:regulation", h.ClassifySingle)
|
||||
iaceRoutes.POST("/projects/:id/hazards", h.CreateHazard)
|
||||
iaceRoutes.GET("/projects/:id/hazards", h.ListHazards)
|
||||
iaceRoutes.PUT("/projects/:id/hazards/:hid", h.UpdateHazard)
|
||||
iaceRoutes.POST("/projects/:id/hazards/suggest", h.SuggestHazards)
|
||||
iaceRoutes.POST("/projects/:id/match-patterns", h.MatchPatterns)
|
||||
iaceRoutes.POST("/projects/:id/parse-narrative", h.ParseNarrative)
|
||||
iaceRoutes.POST("/projects/:id/delta-analysis", h.DeltaAnalysis)
|
||||
iaceRoutes.POST("/projects/:id/llm-gap-review", h.LLMGapReview)
|
||||
iaceRoutes.GET("/projects/:id/fmea/export", h.ExportFMEA)
|
||||
iaceRoutes.POST("/projects/:id/components/:cid/suggest-fms", h.SuggestFailureModes)
|
||||
iaceRoutes.POST("/projects/:id/apply-patterns", h.ApplyPatternResults)
|
||||
iaceRoutes.POST("/projects/:id/hazards/:hid/suggest-measures", h.SuggestMeasuresForHazard)
|
||||
iaceRoutes.POST("/projects/:id/mitigations/:mid/suggest-evidence", h.SuggestEvidenceForMitigation)
|
||||
iaceRoutes.POST("/projects/:id/hazards/:hid/assess", h.AssessRisk)
|
||||
iaceRoutes.GET("/projects/:id/risk-summary", h.GetRiskSummary)
|
||||
iaceRoutes.GET("/projects/:id/suggested-norms", h.SuggestProjectNorms)
|
||||
iaceRoutes.POST("/projects/:id/hazards/:hid/reassess", h.ReassessRisk)
|
||||
|
||||
// Mitigations + evidence + verification.
|
||||
iaceRoutes.GET("/projects/:id/mitigations", h.ListProjectMitigations)
|
||||
iaceRoutes.POST("/projects/:id/hazards/:hid/mitigations", h.CreateMitigation)
|
||||
iaceRoutes.DELETE("/projects/:id/mitigations/:mid", h.DeleteMitigation)
|
||||
iaceRoutes.PUT("/mitigations/:mid", h.UpdateMitigation)
|
||||
iaceRoutes.POST("/mitigations/:mid/verify", h.VerifyMitigation)
|
||||
iaceRoutes.POST("/projects/:id/validate-mitigation-hierarchy", h.ValidateMitigationHierarchy)
|
||||
iaceRoutes.POST("/projects/:id/evidence", h.UploadEvidence)
|
||||
iaceRoutes.GET("/projects/:id/evidence", h.ListEvidence)
|
||||
iaceRoutes.POST("/projects/:id/verification-plan", h.CreateVerificationPlan)
|
||||
iaceRoutes.PUT("/verification-plan/:vid", h.UpdateVerificationPlan)
|
||||
iaceRoutes.POST("/verification-plan/:vid/complete", h.CompleteVerification)
|
||||
iaceRoutes.GET("/projects/:id/verifications", h.ListVerificationPlans)
|
||||
iaceRoutes.POST("/projects/:id/verifications", h.CreateVerificationAlias)
|
||||
iaceRoutes.DELETE("/projects/:id/verifications/:vid", h.DeleteVerificationPlan)
|
||||
iaceRoutes.POST("/projects/:id/verifications/:vid/complete", h.CompleteVerificationAlias)
|
||||
|
||||
// Tech file + monitoring + audit.
|
||||
iaceRoutes.POST("/projects/:id/tech-file/generate", h.GenerateTechFile)
|
||||
iaceRoutes.GET("/projects/:id/tech-file", h.ListTechFileSections)
|
||||
iaceRoutes.PUT("/projects/:id/tech-file/:section", h.UpdateTechFileSection)
|
||||
iaceRoutes.POST("/projects/:id/tech-file/:section/approve", h.ApproveTechFileSection)
|
||||
iaceRoutes.POST("/projects/:id/tech-file/:section/generate", h.GenerateSingleSection)
|
||||
iaceRoutes.GET("/projects/:id/tech-file/export", h.ExportTechFile)
|
||||
iaceRoutes.POST("/projects/:id/monitoring", h.CreateMonitoringEvent)
|
||||
iaceRoutes.GET("/projects/:id/monitoring", h.ListMonitoringEvents)
|
||||
iaceRoutes.PUT("/projects/:id/monitoring/:eid", h.UpdateMonitoringEvent)
|
||||
iaceRoutes.GET("/projects/:id/audit-trail", h.GetAuditTrail)
|
||||
|
||||
// Library + corpus + benchmark.
|
||||
iaceRoutes.POST("/library-search", h.SearchLibrary)
|
||||
iaceRoutes.GET("/ce-corpus-documents", h.ListCECorpusDocuments)
|
||||
iaceRoutes.POST("/projects/:id/initialize", h.InitializeProject)
|
||||
iaceRoutes.GET("/projects/:id/hazard-blocks", h.GetHazardBlocks)
|
||||
iaceRoutes.POST("/projects/:id/benchmark/import-gt", h.ImportGroundTruth)
|
||||
iaceRoutes.GET("/projects/:id/benchmark", h.RunBenchmark)
|
||||
iaceRoutes.GET("/projects/:id/benchmark/summary", h.GetBenchmarkSummary)
|
||||
|
||||
// Regulatory enrichment.
|
||||
iaceRoutes.GET("/projects/:id/hazards/:hid/regulatory-hints", h.EnrichHazardWithRegulations)
|
||||
iaceRoutes.GET("/projects/:id/mitigations/:mid/regulatory-hints", h.EnrichMitigationWithRegulations)
|
||||
iaceRoutes.GET("/projects/:id/regulatory-hints", h.EnrichProjectHazardsBatch)
|
||||
iaceRoutes.POST("/projects/:id/tech-file/:section/enrich", h.EnrichTechFileSection)
|
||||
|
||||
// Production lines.
|
||||
iaceRoutes.POST("/production-lines", h.CreateProductionLine)
|
||||
iaceRoutes.GET("/production-lines", h.ListProductionLines)
|
||||
iaceRoutes.GET("/production-lines/:lid/dashboard", h.GetProductionLineDashboard)
|
||||
iaceRoutes.POST("/production-lines/:lid/stations", h.AddStationToLine)
|
||||
iaceRoutes.DELETE("/production-lines/:lid/stations/:sid", h.RemoveStationFromLine)
|
||||
|
||||
// CE x Compliance crossover + clarifications + customer standards.
|
||||
iaceRoutes.GET("/projects/:id/compliance-triggers", h.GetComplianceTriggers)
|
||||
iaceRoutes.GET("/compliance-faq", h.GetComplianceFAQ)
|
||||
iaceRoutes.GET("/projects/:id/clarifications", h.ListClarifications)
|
||||
iaceRoutes.GET("/projects/:id/clarifications.csv", h.ExportClarificationsCSV)
|
||||
iaceRoutes.GET("/projects/:id/clarifications.html", h.ExportClarificationsHTML)
|
||||
iaceRoutes.GET("/projects/:id/clarifications/:cid/detail", h.ListClarificationDetail)
|
||||
iaceRoutes.POST("/projects/:id/clarifications/:cid/answer", h.AnswerClarification)
|
||||
iaceRoutes.POST("/projects/:id/clarifications/:cid/comment", h.PostClarificationComment)
|
||||
iaceRoutes.GET("/projects/:id/customer-standards", h.ListCustomerStandardSuggestions)
|
||||
iaceRoutes.POST("/projects/:id/customer-standards/import", h.ImportCustomerStandardSuggestion)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package audit
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/breakpilot/ai-compliance-sdk/internal/iace"
|
||||
)
|
||||
|
||||
// runConsistencyImpl asks: does this component, with its own tags PLUS the
|
||||
// tags of its TypicalEnergySources, actually trigger at least one pattern
|
||||
// in every category listed in its TypicalHazardCategories?
|
||||
//
|
||||
// A component declares "this is what I am dangerous for" and the engine
|
||||
// turns that declaration into hazards through patterns. If no pattern can
|
||||
// fire from the component's tag set, the declaration is decorative — the
|
||||
// engine will never produce a hazard in that category for this component,
|
||||
// even though the library author said it should.
|
||||
func init() {
|
||||
runConsistencyImpl = runConsistency
|
||||
}
|
||||
|
||||
func runConsistency() ConsistencyReport {
|
||||
comps := iace.GetComponentLibrary()
|
||||
energies := iace.GetEnergySources()
|
||||
patterns := iace.AllPatterns()
|
||||
|
||||
energyByID := map[string]iace.EnergySourceEntry{}
|
||||
for _, e := range energies {
|
||||
energyByID[e.ID] = e
|
||||
}
|
||||
|
||||
report := ConsistencyReport{TotalComponents: len(comps)}
|
||||
|
||||
for _, c := range comps {
|
||||
if len(c.TypicalHazardCategories) == 0 {
|
||||
report.Consistent++
|
||||
continue
|
||||
}
|
||||
effective := buildEffectiveTags(c, energyByID)
|
||||
covered := categoriesCoveredByPatterns(effective, c.MapsToComponentType, patterns)
|
||||
|
||||
var missing []string
|
||||
for _, cat := range c.TypicalHazardCategories {
|
||||
if !covered[cat] {
|
||||
missing = append(missing, cat)
|
||||
}
|
||||
}
|
||||
if len(missing) == 0 {
|
||||
report.Consistent++
|
||||
continue
|
||||
}
|
||||
|
||||
result := ComponentResult{
|
||||
ComponentID: c.ID,
|
||||
NameDE: c.NameDE,
|
||||
DeclaredCategories: c.TypicalHazardCategories,
|
||||
}
|
||||
for cat := range covered {
|
||||
result.CoveredCategories = append(result.CoveredCategories, cat)
|
||||
}
|
||||
sort.Strings(result.CoveredCategories)
|
||||
for _, cat := range missing {
|
||||
result.MissingForCategories = append(result.MissingForCategories, CategoryGap{
|
||||
Category: cat,
|
||||
SuggestedTags: suggestTagsForCategory(cat, effective, patterns),
|
||||
})
|
||||
}
|
||||
report.Incomplete++
|
||||
report.IncompleteComponents = append(report.IncompleteComponents, result)
|
||||
}
|
||||
|
||||
sort.Slice(report.IncompleteComponents, func(i, j int) bool {
|
||||
return report.IncompleteComponents[i].ComponentID < report.IncompleteComponents[j].ComponentID
|
||||
})
|
||||
return report
|
||||
}
|
||||
|
||||
func buildEffectiveTags(c iace.ComponentLibraryEntry, energyByID map[string]iace.EnergySourceEntry) map[string]bool {
|
||||
set := map[string]bool{}
|
||||
for _, t := range c.Tags {
|
||||
set[t] = true
|
||||
}
|
||||
for _, eID := range c.TypicalEnergySources {
|
||||
e, ok := energyByID[eID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, t := range e.Tags {
|
||||
set[t] = true
|
||||
}
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
// categoriesCoveredByPatterns iterates patterns and finds which
|
||||
// GeneratedHazardCats can fire given the component's effective tags.
|
||||
// We ignore lifecycle, op-state, and human-role filters — those are
|
||||
// project-level. The audit asks "can the library produce ANY hazard in
|
||||
// this category for this component if the project configures everything
|
||||
// reasonably?"
|
||||
func categoriesCoveredByPatterns(tags map[string]bool, _ string, patterns []iace.HazardPattern) map[string]bool {
|
||||
covered := map[string]bool{}
|
||||
for _, p := range patterns {
|
||||
if !tagsCover(tags, p.RequiredComponentTags) {
|
||||
continue
|
||||
}
|
||||
if !tagsCover(tags, p.RequiredEnergyTags) {
|
||||
continue
|
||||
}
|
||||
for _, cat := range p.GeneratedHazardCats {
|
||||
covered[cat] = true
|
||||
}
|
||||
}
|
||||
return covered
|
||||
}
|
||||
|
||||
func tagsCover(have map[string]bool, required []string) bool {
|
||||
for _, t := range required {
|
||||
if !have[t] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// suggestTagsForCategory looks at patterns that DO generate this category
|
||||
// and identifies the tags that would close the gap. Returns the tags most
|
||||
// commonly required by patterns in that category, minus what the component
|
||||
// already has.
|
||||
func suggestTagsForCategory(cat string, have map[string]bool, patterns []iace.HazardPattern) []string {
|
||||
counts := map[string]int{}
|
||||
for _, p := range patterns {
|
||||
matchCat := false
|
||||
for _, c := range p.GeneratedHazardCats {
|
||||
if c == cat {
|
||||
matchCat = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matchCat {
|
||||
continue
|
||||
}
|
||||
for _, t := range p.RequiredComponentTags {
|
||||
if !have[t] {
|
||||
counts[t]++
|
||||
}
|
||||
}
|
||||
for _, t := range p.RequiredEnergyTags {
|
||||
if !have[t] {
|
||||
counts[t]++
|
||||
}
|
||||
}
|
||||
}
|
||||
type kv struct {
|
||||
tag string
|
||||
n int
|
||||
}
|
||||
var sorted []kv
|
||||
for t, n := range counts {
|
||||
sorted = append(sorted, kv{t, n})
|
||||
}
|
||||
sort.Slice(sorted, func(i, j int) bool { return sorted[i].n > sorted[j].n })
|
||||
var out []string
|
||||
for i, s := range sorted {
|
||||
if i >= 6 {
|
||||
break
|
||||
}
|
||||
out = append(out, s.tag)
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package audit
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// runEchoImpl checks if each meaningful phrase from the limits-form is
|
||||
// echoed by at least one generated hazard. A phrase that names a concrete
|
||||
// scenario, fault, or constraint must reappear (semantically) in some
|
||||
// hazard's name, scenario, or description. Phrases without echo are gaps:
|
||||
// the engineer documented the risk but the engine never lifted it into
|
||||
// the hazard register.
|
||||
//
|
||||
// Echo detection here is a lightweight Jaccard overlap of content tokens
|
||||
// (not embeddings) — robust enough for the demonstrative diagnostic and
|
||||
// keeps the audit fully deterministic without an external model. The
|
||||
// caller can later swap in a vector-based scorer.
|
||||
func init() {
|
||||
runEchoImpl = runEcho
|
||||
}
|
||||
|
||||
// Significant limits-form fields. Each item is (key, label). We only
|
||||
// audit the freeform fields where engineers describe risks — list/enum
|
||||
// fields (operating_modes, person_groups, industry_sectors) are out of
|
||||
// scope because they carry no narrative phrases.
|
||||
var echoFields = []struct {
|
||||
key string
|
||||
label string
|
||||
}{
|
||||
{"general_description", "Allg. Beschreibung"},
|
||||
{"intended_purpose", "Bestimmungsgemaesse Verwendung"},
|
||||
{"variants", "Varianten"},
|
||||
{"foreseeable_misuses", "Vorhersehbare Fehlanwendung"},
|
||||
{"spatial_limits", "Raeumliche Grenzen"},
|
||||
{"temporal_limits", "Zeitliche Grenzen"},
|
||||
{"operating_conditions", "Betriebsbedingungen"},
|
||||
{"energy_supply", "Energieversorgung"},
|
||||
{"mechanical_interfaces", "Mechanische Schnittstellen"},
|
||||
{"electrical_interfaces", "Elektrische Schnittstellen"},
|
||||
{"software_interfaces", "Software-Schnittstellen"},
|
||||
{"pneumatic_hydraulic_interfaces", "Pneumatik/Hydraulik"},
|
||||
{"qualification_requirements", "Personenqualifikation"},
|
||||
}
|
||||
|
||||
var sentenceSplit = regexp.MustCompile(`[.!?]\s+|\n+`)
|
||||
var wordRE = regexp.MustCompile(`[a-zäöüßA-ZÄÖÜ]{4,}`)
|
||||
|
||||
// echoThreshold — minimum Jaccard overlap (between sentence content
|
||||
// tokens and a hazard's content tokens) above which the sentence is
|
||||
// considered echoed. Tuned by hand to give meaningful results without a
|
||||
// labeled corpus; the audit reports the actual best score for each
|
||||
// orphaned phrase so a human can re-tune if needed.
|
||||
const echoThreshold = 0.18
|
||||
|
||||
func runEcho(form map[string]any, hazards []map[string]any) EchoReport {
|
||||
limits := unwrapLimits(form)
|
||||
|
||||
// Precompute hazard token bags once
|
||||
type bag struct {
|
||||
tokens map[string]bool
|
||||
text string
|
||||
}
|
||||
var hazardBags []bag
|
||||
for _, h := range hazards {
|
||||
txt := joinHazardText(h)
|
||||
toks := contentTokenSet(txt)
|
||||
hazardBags = append(hazardBags, bag{tokens: toks, text: txt})
|
||||
}
|
||||
|
||||
report := EchoReport{}
|
||||
for _, fld := range echoFields {
|
||||
raw, _ := limits[fld.key].(string)
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
continue
|
||||
}
|
||||
for _, sent := range sentenceSplit.Split(raw, -1) {
|
||||
sent = strings.TrimSpace(sent)
|
||||
if len(sent) < 30 {
|
||||
// Skip very short fragments
|
||||
continue
|
||||
}
|
||||
report.TotalPhrases++
|
||||
st := contentTokenSet(sent)
|
||||
if len(st) < 3 {
|
||||
continue
|
||||
}
|
||||
bestScore := 0.0
|
||||
for _, hb := range hazardBags {
|
||||
score := jaccard(st, hb.tokens)
|
||||
if score > bestScore {
|
||||
bestScore = score
|
||||
}
|
||||
}
|
||||
if bestScore >= echoThreshold {
|
||||
report.Echoed++
|
||||
continue
|
||||
}
|
||||
report.Orphaned++
|
||||
report.OrphanedPhrases = append(report.OrphanedPhrases, OrphanedPhrase{
|
||||
Field: fld.label,
|
||||
Phrase: sent,
|
||||
BestScore: bestScore,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(report.OrphanedPhrases, func(i, j int) bool {
|
||||
// Lowest scores first — most clearly orphaned
|
||||
return report.OrphanedPhrases[i].BestScore < report.OrphanedPhrases[j].BestScore
|
||||
})
|
||||
return report
|
||||
}
|
||||
|
||||
func unwrapLimits(form map[string]any) map[string]any {
|
||||
if inner, ok := form["limits_form"].(map[string]any); ok {
|
||||
return inner
|
||||
}
|
||||
return form
|
||||
}
|
||||
|
||||
func joinHazardText(h map[string]any) string {
|
||||
parts := []string{}
|
||||
for _, k := range []string{"name", "description", "scenario", "trigger_event", "possible_harm", "hazardous_zone", "category", "sub_category"} {
|
||||
if v, ok := h[k].(string); ok {
|
||||
parts = append(parts, v)
|
||||
}
|
||||
}
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
func contentTokenSet(s string) map[string]bool {
|
||||
out := map[string]bool{}
|
||||
for _, m := range wordRE.FindAllString(s, -1) {
|
||||
w := strings.ToLower(m)
|
||||
if stopWords[w] {
|
||||
continue
|
||||
}
|
||||
out[w] = true
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func jaccard(a, b map[string]bool) float64 {
|
||||
if len(a) == 0 || len(b) == 0 {
|
||||
return 0
|
||||
}
|
||||
inter := 0
|
||||
for x := range a {
|
||||
if b[x] {
|
||||
inter++
|
||||
}
|
||||
}
|
||||
union := len(a) + len(b) - inter
|
||||
if union == 0 {
|
||||
return 0
|
||||
}
|
||||
return float64(inter) / float64(union)
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package audit
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// runHierarchyImpl checks the ISO 12100 / EN 12100 risk-reduction
|
||||
// hierarchy on the generated mitigation set: every safety-relevant
|
||||
// hazard should have at least one "inherently safe design" measure
|
||||
// (design) and additionally either a guarding/protective device
|
||||
// (protection) or an information-for-use measure (information).
|
||||
//
|
||||
// Cyber-, ergonomic-, and software-only hazards have looser
|
||||
// expectations — design alone or information alone may legitimately
|
||||
// suffice. The audit reports which level is missing, not whether the
|
||||
// remaining measures are individually correct. That is a different
|
||||
// check (E2 — semantic quality), out of scope here.
|
||||
func init() {
|
||||
runHierarchyImpl = runHierarchy
|
||||
}
|
||||
|
||||
// hazardExpectsProtection lists hazard categories where a pure
|
||||
// design+information combination is usually not enough — the engine
|
||||
// should produce at least one explicit protective measure (guard,
|
||||
// interlock, sensor, presence detector, …).
|
||||
var hazardExpectsProtection = map[string]bool{
|
||||
"mechanical_hazard": true,
|
||||
"electrical_hazard": true,
|
||||
"thermal_hazard": true,
|
||||
"pneumatic_hydraulic": true,
|
||||
"radiation_hazard": true,
|
||||
"laser_hazard": true,
|
||||
"fire_explosion_hazard": true,
|
||||
"chemical_hazard": true,
|
||||
}
|
||||
|
||||
func runHierarchy(hazards, mitigations []map[string]any) HierarchyReport {
|
||||
report := HierarchyReport{TotalHazards: len(hazards)}
|
||||
|
||||
// Index mitigations by hazard_id
|
||||
byHazard := map[string][]map[string]any{}
|
||||
for _, m := range mitigations {
|
||||
hid, _ := m["hazard_id"].(string)
|
||||
if hid == "" {
|
||||
continue
|
||||
}
|
||||
byHazard[hid] = append(byHazard[hid], m)
|
||||
}
|
||||
|
||||
for _, h := range hazards {
|
||||
hid, _ := h["id"].(string)
|
||||
category, _ := h["category"].(string)
|
||||
name, _ := h["name"].(string)
|
||||
|
||||
levels := levelsForHazard(byHazard[hid])
|
||||
missing := expectedMissing(category, levels)
|
||||
|
||||
if len(missing) == 0 {
|
||||
report.Complete++
|
||||
continue
|
||||
}
|
||||
for _, m := range missing {
|
||||
switch m {
|
||||
case "design":
|
||||
report.MissingDesign++
|
||||
case "protection":
|
||||
report.MissingProtection++
|
||||
case "information":
|
||||
report.MissingInfo++
|
||||
}
|
||||
}
|
||||
report.IncompleteHazards = append(report.IncompleteHazards, HazardHierarchyResult{
|
||||
HazardID: hid,
|
||||
Name: name,
|
||||
Category: category,
|
||||
Levels: levels,
|
||||
MissingLevels: missing,
|
||||
})
|
||||
}
|
||||
|
||||
// Sort: protection-missing first (most consequential), then by category
|
||||
sort.Slice(report.IncompleteHazards, func(i, j int) bool {
|
||||
a := report.IncompleteHazards[i]
|
||||
b := report.IncompleteHazards[j]
|
||||
ap := contains(a.MissingLevels, "protection")
|
||||
bp := contains(b.MissingLevels, "protection")
|
||||
if ap != bp {
|
||||
return ap
|
||||
}
|
||||
return a.Category < b.Category
|
||||
})
|
||||
return report
|
||||
}
|
||||
|
||||
// levelsForHazard returns the distinct reduction-type levels present
|
||||
// for a hazard's mitigation set. Possible values: design, protection,
|
||||
// information.
|
||||
func levelsForHazard(mits []map[string]any) []string {
|
||||
seen := map[string]bool{}
|
||||
for _, m := range mits {
|
||||
rt, _ := m["reduction_type"].(string)
|
||||
switch strings.ToLower(rt) {
|
||||
case "design":
|
||||
seen["design"] = true
|
||||
case "protection", "protective":
|
||||
seen["protection"] = true
|
||||
case "information":
|
||||
seen["information"] = true
|
||||
}
|
||||
}
|
||||
var out []string
|
||||
for k := range seen {
|
||||
out = append(out, k)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// expectedMissing returns the levels that the hierarchy demands but
|
||||
// the mitigation set does not provide.
|
||||
//
|
||||
// Rule:
|
||||
// - Every hazard with mitigations should have a design measure.
|
||||
// - Categories in hazardExpectsProtection additionally need a
|
||||
// protection measure.
|
||||
// - All hazards should have an information measure unless they
|
||||
// already have both design + protection (the information layer
|
||||
// can then be considered subsumed for the audit's purpose; the
|
||||
// real engine usually still adds it).
|
||||
func expectedMissing(category string, present []string) []string {
|
||||
have := toBoolSet(present)
|
||||
var missing []string
|
||||
if !have["design"] {
|
||||
missing = append(missing, "design")
|
||||
}
|
||||
if hazardExpectsProtection[category] && !have["protection"] {
|
||||
missing = append(missing, "protection")
|
||||
}
|
||||
// Information is only flagged if both design and protection are
|
||||
// also absent — otherwise too noisy. We still surface the case
|
||||
// where information is the SOLE present level: that means the
|
||||
// hazard is mitigated only by warning labels, which is rarely
|
||||
// adequate.
|
||||
if !have["information"] && !have["design"] && !have["protection"] {
|
||||
missing = append(missing, "information")
|
||||
}
|
||||
return missing
|
||||
}
|
||||
|
||||
func contains(list []string, target string) bool {
|
||||
for _, x := range list {
|
||||
if x == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package audit
|
||||
|
||||
// Implementation entry points for Methods B-E. The full algorithms live
|
||||
// in consistency.go, vocabulary.go, echo.go, hierarchy.go respectively.
|
||||
// Until those files land, these wrappers keep main.go compilable and
|
||||
// return a clearly-marked empty report.
|
||||
|
||||
func RunConsistency() ConsistencyReport {
|
||||
return runConsistencyImpl()
|
||||
}
|
||||
|
||||
func RunVocabulary(form map[string]any) VocabularyReport {
|
||||
return runVocabularyImpl(form)
|
||||
}
|
||||
|
||||
func RunEcho(form map[string]any, hazards []map[string]any) EchoReport {
|
||||
return runEchoImpl(form, hazards)
|
||||
}
|
||||
|
||||
func RunHierarchy(hazards, mitigations []map[string]any) HierarchyReport {
|
||||
return runHierarchyImpl(hazards, mitigations)
|
||||
}
|
||||
|
||||
// Default implementations — replaced when each method file lands.
|
||||
// Keeping them as separate functions in one place avoids name clashes
|
||||
// once consistency.go etc. add their real implementations.
|
||||
|
||||
var (
|
||||
runConsistencyImpl = func() ConsistencyReport { return ConsistencyReport{} }
|
||||
runVocabularyImpl = func(form map[string]any) VocabularyReport { return VocabularyReport{} }
|
||||
runEchoImpl = func(form map[string]any, hazards []map[string]any) EchoReport {
|
||||
return EchoReport{}
|
||||
}
|
||||
runHierarchyImpl = func(hazards, mitigations []map[string]any) HierarchyReport {
|
||||
return HierarchyReport{}
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,298 @@
|
||||
// Package audit provides static and runtime audits of the IACE pattern
|
||||
// engine — finding pattern reachability, library consistency, and
|
||||
// limits-form coverage gaps without a ground-truth reference.
|
||||
package audit
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/breakpilot/ai-compliance-sdk/internal/iace"
|
||||
)
|
||||
|
||||
// ReachabilityResult is the verdict for a single pattern in Method A.
|
||||
type ReachabilityResult struct {
|
||||
PatternID string `json:"pattern_id"`
|
||||
Name string `json:"name_de"`
|
||||
Priority int `json:"priority"`
|
||||
RequiredAllTags []string `json:"required_tags"`
|
||||
UnreachableTags []string `json:"unreachable_tags,omitempty"`
|
||||
Status string `json:"status"` // "reachable" | "weakly_reachable" | "unreachable"
|
||||
ReachableSources []string `json:"reachable_sources,omitempty"`
|
||||
FixSuggestions []string `json:"fix_suggestions,omitempty"`
|
||||
}
|
||||
|
||||
// ReachabilityReport is the full Method A output.
|
||||
type ReachabilityReport struct {
|
||||
TotalPatterns int `json:"total_patterns"`
|
||||
Reachable int `json:"reachable"`
|
||||
WeaklyReachable int `json:"weakly_reachable"`
|
||||
Unreachable int `json:"unreachable"`
|
||||
UniverseTags []string `json:"universe_tags"`
|
||||
UnreachablePatterns []ReachabilityResult `json:"unreachable_patterns"`
|
||||
WeakPatterns []ReachabilityResult `json:"weak_patterns"`
|
||||
}
|
||||
|
||||
// RunReachability evaluates every pattern against the achievable tag universe.
|
||||
//
|
||||
// A pattern is:
|
||||
// - "unreachable" if at least one required tag is not produced by any
|
||||
// component, energy source, or keyword-dictionary entry.
|
||||
// - "weakly_reachable" if all required tags exist in the universe but
|
||||
// no single source (one Component or one EnergySource or one Keyword
|
||||
// entry) supplies all of them at once — i.e., it relies on multiple
|
||||
// parser hits to combine.
|
||||
// - "reachable" if some single source covers all required tags.
|
||||
//
|
||||
// The classification ignores ExcludedComponentTags and runtime filters
|
||||
// (lifecycle/op-state/machine-type), because those are project-level
|
||||
// concerns. The audit answers "could this pattern EVER fire", not
|
||||
// "does it fire for project X".
|
||||
func RunReachability() ReachabilityReport {
|
||||
patterns := iace.AllPatterns()
|
||||
comps := iace.GetComponentLibrary()
|
||||
energies := iace.GetEnergySources()
|
||||
keywords := iace.GetKeywordDictionary()
|
||||
|
||||
// Tag universe: union of every tag emitted anywhere
|
||||
universe := map[string][]string{} // tag → list of source IDs that emit it
|
||||
for _, c := range comps {
|
||||
for _, t := range c.Tags {
|
||||
universe[t] = appendUnique(universe[t], "component:"+c.ID)
|
||||
}
|
||||
}
|
||||
for _, e := range energies {
|
||||
for _, t := range e.Tags {
|
||||
universe[t] = appendUnique(universe[t], "energy:"+e.ID)
|
||||
}
|
||||
}
|
||||
for i, kw := range keywords {
|
||||
for _, t := range kw.ExtraTags {
|
||||
universe[t] = appendUnique(universe[t], keywordLabel(kw, i))
|
||||
}
|
||||
// Keyword entries can also reference components/energies, which
|
||||
// transitively add their tags to the keyword's effective tag set.
|
||||
for _, cID := range kw.ComponentIDs {
|
||||
for _, c := range comps {
|
||||
if c.ID != cID {
|
||||
continue
|
||||
}
|
||||
for _, t := range c.Tags {
|
||||
universe[t] = appendUnique(universe[t], keywordLabel(kw, i))
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, eID := range kw.EnergyIDs {
|
||||
for _, e := range energies {
|
||||
if e.ID != eID {
|
||||
continue
|
||||
}
|
||||
for _, t := range e.Tags {
|
||||
universe[t] = appendUnique(universe[t], keywordLabel(kw, i))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Single-source coverage map: tag → covering sources, but also
|
||||
// per-source tag set so we can check "is there ONE source covering
|
||||
// all required tags".
|
||||
sourceTags := map[string]map[string]bool{}
|
||||
for _, c := range comps {
|
||||
key := "component:" + c.ID
|
||||
sourceTags[key] = toBoolSet(c.Tags)
|
||||
}
|
||||
for _, e := range energies {
|
||||
key := "energy:" + e.ID
|
||||
sourceTags[key] = toBoolSet(e.Tags)
|
||||
}
|
||||
for i, kw := range keywords {
|
||||
key := keywordLabel(kw, i)
|
||||
set := toBoolSet(kw.ExtraTags)
|
||||
for _, cID := range kw.ComponentIDs {
|
||||
for _, c := range comps {
|
||||
if c.ID == cID {
|
||||
for _, t := range c.Tags {
|
||||
set[t] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, eID := range kw.EnergyIDs {
|
||||
for _, e := range energies {
|
||||
if e.ID == eID {
|
||||
for _, t := range e.Tags {
|
||||
set[t] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sourceTags[key] = set
|
||||
}
|
||||
|
||||
report := ReachabilityReport{TotalPatterns: len(patterns)}
|
||||
|
||||
// Universe tag list (sorted) for the report header
|
||||
for t := range universe {
|
||||
report.UniverseTags = append(report.UniverseTags, t)
|
||||
}
|
||||
sort.Strings(report.UniverseTags)
|
||||
|
||||
for _, p := range patterns {
|
||||
all := dedup(append(append([]string{}, p.RequiredComponentTags...), p.RequiredEnergyTags...))
|
||||
if len(all) == 0 {
|
||||
// Pattern with no tag requirements relies on lifecycle/machine_type
|
||||
// filters only — count as reachable by default.
|
||||
report.Reachable++
|
||||
continue
|
||||
}
|
||||
|
||||
var missing []string
|
||||
for _, t := range all {
|
||||
if _, ok := universe[t]; !ok {
|
||||
missing = append(missing, t)
|
||||
}
|
||||
}
|
||||
|
||||
res := ReachabilityResult{
|
||||
PatternID: p.ID,
|
||||
Name: p.NameDE,
|
||||
Priority: p.Priority,
|
||||
RequiredAllTags: all,
|
||||
}
|
||||
|
||||
if len(missing) > 0 {
|
||||
res.Status = "unreachable"
|
||||
res.UnreachableTags = missing
|
||||
res.FixSuggestions = suggestFixes(p, missing, comps, sourceTags)
|
||||
report.Unreachable++
|
||||
report.UnreachablePatterns = append(report.UnreachablePatterns, res)
|
||||
continue
|
||||
}
|
||||
|
||||
// All tags in universe — check single-source coverage
|
||||
single := findSingleSourceCovers(all, sourceTags)
|
||||
if len(single) > 0 {
|
||||
res.Status = "reachable"
|
||||
res.ReachableSources = single
|
||||
report.Reachable++
|
||||
continue
|
||||
}
|
||||
|
||||
res.Status = "weakly_reachable"
|
||||
res.FixSuggestions = suggestSingleSourceFixes(p, all, comps, sourceTags)
|
||||
report.WeaklyReachable++
|
||||
report.WeakPatterns = append(report.WeakPatterns, res)
|
||||
}
|
||||
|
||||
sort.Slice(report.UnreachablePatterns, func(i, j int) bool {
|
||||
return report.UnreachablePatterns[i].Priority > report.UnreachablePatterns[j].Priority
|
||||
})
|
||||
sort.Slice(report.WeakPatterns, func(i, j int) bool {
|
||||
return report.WeakPatterns[i].Priority > report.WeakPatterns[j].Priority
|
||||
})
|
||||
return report
|
||||
}
|
||||
|
||||
func findSingleSourceCovers(required []string, sourceTags map[string]map[string]bool) []string {
|
||||
var hits []string
|
||||
for src, tags := range sourceTags {
|
||||
ok := true
|
||||
for _, t := range required {
|
||||
if !tags[t] {
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
hits = append(hits, src)
|
||||
}
|
||||
}
|
||||
sort.Strings(hits)
|
||||
return hits
|
||||
}
|
||||
|
||||
// suggestFixes proposes concrete library edits for unreachable patterns:
|
||||
// "Add tag X to Component C014 (Hubwerk)" type suggestions.
|
||||
func suggestFixes(p iace.HazardPattern, missing []string, comps []iace.ComponentLibraryEntry, sourceTags map[string]map[string]bool) []string {
|
||||
var out []string
|
||||
// For each missing tag, find candidates: components/energies that
|
||||
// would semantically own that tag based on existing tags overlap.
|
||||
for _, tag := range missing {
|
||||
candidates := nearComponents(p, tag, comps, sourceTags)
|
||||
if len(candidates) > 0 {
|
||||
out = append(out, "Add tag '"+tag+"' to one of: "+joinFirst(candidates, 3))
|
||||
} else {
|
||||
out = append(out, "Tag '"+tag+"' is undefined anywhere — needs a new component or energy source carrying it")
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func suggestSingleSourceFixes(p iace.HazardPattern, all []string, comps []iace.ComponentLibraryEntry, sourceTags map[string]map[string]bool) []string {
|
||||
// Find components that match the most required tags, then suggest
|
||||
// adding the residual ones.
|
||||
best := ""
|
||||
bestCover := 0
|
||||
var bestMissing []string
|
||||
for src, tags := range sourceTags {
|
||||
hit := 0
|
||||
var miss []string
|
||||
for _, t := range all {
|
||||
if tags[t] {
|
||||
hit++
|
||||
} else {
|
||||
miss = append(miss, t)
|
||||
}
|
||||
}
|
||||
if hit > bestCover {
|
||||
best, bestCover, bestMissing = src, hit, miss
|
||||
}
|
||||
}
|
||||
if best == "" || bestCover == 0 {
|
||||
return []string{"No single source covers any required tags — pattern needs a new dedicated component"}
|
||||
}
|
||||
if len(bestMissing) == 0 {
|
||||
return nil
|
||||
}
|
||||
return []string{"Closest single source '" + best + "' covers " + itoa(bestCover) + "/" + itoa(len(all)) + " tags. Add missing tags to it: " + joinFirst(bestMissing, 5)}
|
||||
}
|
||||
|
||||
// nearComponents finds components whose tags overlap most with the pattern's
|
||||
// requirements — these are good candidates to receive the missing tag.
|
||||
func nearComponents(p iace.HazardPattern, missing string, comps []iace.ComponentLibraryEntry, sourceTags map[string]map[string]bool) []string {
|
||||
required := dedup(append(append([]string{}, p.RequiredComponentTags...), p.RequiredEnergyTags...))
|
||||
required = removeOne(required, missing)
|
||||
if len(required) == 0 {
|
||||
return nil
|
||||
}
|
||||
type scored struct {
|
||||
id string
|
||||
score int
|
||||
}
|
||||
var scoredList []scored
|
||||
for _, c := range comps {
|
||||
tagSet := toBoolSet(c.Tags)
|
||||
s := 0
|
||||
for _, t := range required {
|
||||
if tagSet[t] {
|
||||
s++
|
||||
}
|
||||
}
|
||||
if s > 0 {
|
||||
scoredList = append(scoredList, scored{id: c.ID + " (" + c.NameDE + ")", score: s})
|
||||
}
|
||||
}
|
||||
sort.Slice(scoredList, func(i, j int) bool { return scoredList[i].score > scoredList[j].score })
|
||||
var out []string
|
||||
for _, s := range scoredList {
|
||||
out = append(out, s.id)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func keywordLabel(kw iace.KeywordEntry, idx int) string {
|
||||
if len(kw.Keywords) > 0 {
|
||||
return "keyword:" + kw.Keywords[0]
|
||||
}
|
||||
return "keyword:" + itoa(idx)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package audit
|
||||
|
||||
// Stubs for Methods B-E. Each is filled in its own file as the audit
|
||||
// suite grows. Keeping the type contracts here lets the CLI compile
|
||||
// before each method has its full implementation.
|
||||
|
||||
// ============================================================================
|
||||
// Method B — Component Self-Consistency
|
||||
// ============================================================================
|
||||
|
||||
type CategoryGap struct {
|
||||
Category string `json:"category"`
|
||||
SuggestedTags []string `json:"suggested_tags"`
|
||||
}
|
||||
|
||||
type ComponentResult struct {
|
||||
ComponentID string `json:"component_id"`
|
||||
NameDE string `json:"name_de"`
|
||||
DeclaredCategories []string `json:"declared_categories"`
|
||||
CoveredCategories []string `json:"covered_categories"`
|
||||
MissingForCategories []CategoryGap `json:"missing_for_categories,omitempty"`
|
||||
}
|
||||
|
||||
type ConsistencyReport struct {
|
||||
TotalComponents int `json:"total_components"`
|
||||
Consistent int `json:"consistent"`
|
||||
Incomplete int `json:"incomplete"`
|
||||
IncompleteComponents []ComponentResult `json:"incomplete_components"`
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Method C — Limits-Form Vocabulary Diff
|
||||
// ============================================================================
|
||||
|
||||
type DictionarySuggestion struct {
|
||||
Token string `json:"token"`
|
||||
Field string `json:"field"`
|
||||
PatternIDs []string `json:"pattern_ids"`
|
||||
}
|
||||
|
||||
type VocabularyReport struct {
|
||||
UniqueTokens int `json:"unique_tokens"`
|
||||
KnownTokens []string `json:"known_tokens"`
|
||||
UnknownTokens []string `json:"unknown_tokens"`
|
||||
SuggestedDictionaryEntries []DictionarySuggestion `json:"suggested_dictionary_entries"`
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Method D — Limits-Form Echo
|
||||
// ============================================================================
|
||||
|
||||
type OrphanedPhrase struct {
|
||||
Field string `json:"field"`
|
||||
Phrase string `json:"phrase"`
|
||||
BestScore float64 `json:"best_score"`
|
||||
}
|
||||
|
||||
type EchoReport struct {
|
||||
TotalPhrases int `json:"total_phrases"`
|
||||
Echoed int `json:"echoed"`
|
||||
Orphaned int `json:"orphaned"`
|
||||
OrphanedPhrases []OrphanedPhrase `json:"orphaned_phrases"`
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Method E — Hierarchy Completeness
|
||||
// ============================================================================
|
||||
|
||||
type HazardHierarchyResult struct {
|
||||
HazardID string `json:"hazard_id"`
|
||||
Name string `json:"name"`
|
||||
Category string `json:"category"`
|
||||
Levels []string `json:"present_levels"`
|
||||
MissingLevels []string `json:"missing_levels"`
|
||||
}
|
||||
|
||||
type HierarchyReport struct {
|
||||
TotalHazards int `json:"total_hazards"`
|
||||
Complete int `json:"complete"`
|
||||
MissingDesign int `json:"missing_design"`
|
||||
MissingProtection int `json:"missing_protection"`
|
||||
MissingInfo int `json:"missing_information"`
|
||||
IncompleteHazards []HazardHierarchyResult `json:"incomplete_hazards"`
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package audit
|
||||
|
||||
import "strconv"
|
||||
|
||||
func appendUnique(list []string, item string) []string {
|
||||
for _, x := range list {
|
||||
if x == item {
|
||||
return list
|
||||
}
|
||||
}
|
||||
return append(list, item)
|
||||
}
|
||||
|
||||
func toBoolSet(list []string) map[string]bool {
|
||||
s := make(map[string]bool, len(list))
|
||||
for _, x := range list {
|
||||
s[x] = true
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func dedup(list []string) []string {
|
||||
seen := map[string]bool{}
|
||||
var out []string
|
||||
for _, x := range list {
|
||||
if !seen[x] {
|
||||
seen[x] = true
|
||||
out = append(out, x)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func removeOne(list []string, item string) []string {
|
||||
out := make([]string, 0, len(list))
|
||||
for _, x := range list {
|
||||
if x != item {
|
||||
out = append(out, x)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func joinFirst(list []string, n int) string {
|
||||
if len(list) <= n {
|
||||
return joinAll(list)
|
||||
}
|
||||
return joinAll(list[:n]) + ", ..."
|
||||
}
|
||||
|
||||
func joinAll(list []string) string {
|
||||
s := ""
|
||||
for i, x := range list {
|
||||
if i > 0 {
|
||||
s += ", "
|
||||
}
|
||||
s += x
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func itoa(n int) string { return strconv.Itoa(n) }
|
||||
@@ -0,0 +1,153 @@
|
||||
package audit
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/breakpilot/ai-compliance-sdk/internal/iace"
|
||||
)
|
||||
|
||||
// runVocabularyImpl takes a limits-form payload (the structured machine
|
||||
// description filled in by the engineer) and asks: which of its words
|
||||
// are unknown to the keyword dictionary yet appear in any pattern's
|
||||
// scenario/trigger/harm/zone text? Each such word is a dictionary gap —
|
||||
// the engineer typed a term that some pattern is waiting for, but the
|
||||
// parser cannot translate it into a tag.
|
||||
func init() {
|
||||
runVocabularyImpl = runVocabulary
|
||||
}
|
||||
|
||||
var tokenRE = regexp.MustCompile(`[a-zäöüßA-ZÄÖÜ]{4,}`)
|
||||
|
||||
// German + English stop words that show up in any narrative but carry
|
||||
// no engineering meaning. Kept short on purpose — we only want to drop
|
||||
// obvious filler.
|
||||
var stopWords = map[string]bool{
|
||||
"oder": true, "und": true, "auch": true, "wenn": true, "wird": true,
|
||||
"werden": true, "kann": true, "koennen": true, "soll": true, "muss": true,
|
||||
"sind": true, "eine": true, "einer": true, "einem": true, "einen": true,
|
||||
"diese": true, "dieser": true, "dieses": true, "diesem": true, "diesen": true,
|
||||
"durch": true, "nach": true, "ueber": true, "unter": true, "zwischen": true,
|
||||
"nicht": true, "ohne": true, "fuer": true, "bzw": true, "etc": true,
|
||||
"sowie": true, "siehe": true, "etwa": true, "ggf": true, "the": true,
|
||||
"with": true, "from": true, "this": true, "that": true, "have": true,
|
||||
"insbesondere": true, "ausschliesslich": true, "ebenfalls": true,
|
||||
"jeweils": true, "weitere": true, "weiteren": true, "weiterer": true,
|
||||
}
|
||||
|
||||
func runVocabulary(form map[string]any) VocabularyReport {
|
||||
limits, ok := form["limits_form"].(map[string]any)
|
||||
if !ok {
|
||||
// Form may already be the inner object
|
||||
limits = form
|
||||
}
|
||||
|
||||
tokens := map[string]bool{}
|
||||
for _, v := range limits {
|
||||
extractTokens(v, tokens)
|
||||
}
|
||||
report := VocabularyReport{UniqueTokens: len(tokens)}
|
||||
|
||||
dictTokens := dictionaryVocabulary()
|
||||
|
||||
for tok := range tokens {
|
||||
if stopWords[tok] {
|
||||
continue
|
||||
}
|
||||
if dictTokenHit(tok, dictTokens) {
|
||||
report.KnownTokens = append(report.KnownTokens, tok)
|
||||
} else {
|
||||
report.UnknownTokens = append(report.UnknownTokens, tok)
|
||||
}
|
||||
}
|
||||
sort.Strings(report.KnownTokens)
|
||||
sort.Strings(report.UnknownTokens)
|
||||
|
||||
// For each unknown token check if any pattern names it
|
||||
patterns := iace.AllPatterns()
|
||||
for _, tok := range report.UnknownTokens {
|
||||
hits := patternsMentioning(tok, patterns)
|
||||
if len(hits) == 0 {
|
||||
continue
|
||||
}
|
||||
report.SuggestedDictionaryEntries = append(report.SuggestedDictionaryEntries, DictionarySuggestion{
|
||||
Token: tok,
|
||||
PatternIDs: hits,
|
||||
})
|
||||
}
|
||||
sort.Slice(report.SuggestedDictionaryEntries, func(i, j int) bool {
|
||||
return len(report.SuggestedDictionaryEntries[i].PatternIDs) > len(report.SuggestedDictionaryEntries[j].PatternIDs)
|
||||
})
|
||||
return report
|
||||
}
|
||||
|
||||
func extractTokens(v any, out map[string]bool) {
|
||||
switch x := v.(type) {
|
||||
case string:
|
||||
for _, m := range tokenRE.FindAllString(x, -1) {
|
||||
out[strings.ToLower(m)] = true
|
||||
}
|
||||
case []any:
|
||||
for _, e := range x {
|
||||
extractTokens(e, out)
|
||||
}
|
||||
case map[string]any:
|
||||
for _, e := range x {
|
||||
extractTokens(e, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dictionaryVocabulary builds the lowercase set of all keyword strings
|
||||
// that the parser will recognize, including normalized forms (umlauts
|
||||
// replaced like in the keyword dictionary).
|
||||
func dictionaryVocabulary() map[string]bool {
|
||||
out := map[string]bool{}
|
||||
for _, kw := range iace.GetKeywordDictionary() {
|
||||
for _, k := range kw.Keywords {
|
||||
out[strings.ToLower(k)] = true
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// dictTokenHit returns true if the token would be matched by any
|
||||
// dictionary entry. Dictionary entries can be substrings, so we treat
|
||||
// the dict as a set of stem-like matchers: a token is "known" if it
|
||||
// equals a dict word OR contains a dict word as substring OR the dict
|
||||
// word contains the token.
|
||||
func dictTokenHit(tok string, dict map[string]bool) bool {
|
||||
if dict[tok] {
|
||||
return true
|
||||
}
|
||||
for d := range dict {
|
||||
if strings.Contains(tok, d) || strings.Contains(d, tok) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// patternsMentioning returns up to 8 pattern IDs whose scenario/trigger/
|
||||
// harm/zone text contains the token (case-insensitive substring).
|
||||
func patternsMentioning(tok string, patterns []iace.HazardPattern) []string {
|
||||
tokLower := strings.ToLower(tok)
|
||||
seen := map[string]bool{}
|
||||
var out []string
|
||||
for _, p := range patterns {
|
||||
hay := strings.ToLower(p.ScenarioDE + " " + p.TriggerDE + " " + p.HarmDE + " " + p.ZoneDE + " " + p.NameDE)
|
||||
if !strings.Contains(hay, tokLower) {
|
||||
continue
|
||||
}
|
||||
if seen[p.ID] {
|
||||
continue
|
||||
}
|
||||
seen[p.ID] = true
|
||||
out = append(out, p.ID)
|
||||
if len(out) >= 8 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -104,39 +104,14 @@ func GetProjectComplianceTriggers(hazards []Hazard, patterns []HazardPattern) *C
|
||||
}
|
||||
}
|
||||
|
||||
// AllPatterns returns every hazard pattern from all pattern sources.
|
||||
// This mirrors the aggregation in NewPatternEngine but returns just the slice.
|
||||
// AllPatterns returns every registered hazard pattern. Delegates to
|
||||
// collectAllPatterns() in pattern_registry.go so new pattern sources only
|
||||
// need to be added in one place. Pre-2026-05-21 this function maintained
|
||||
// a duplicate enumeration which silently drifted from the registry —
|
||||
// CRA, ISO12100-gap, robot-cell, CNC, VDMA, textile-agri, GT-bremse and
|
||||
// secondary-harm patterns were invisible to AllPatterns callers.
|
||||
func AllPatterns() []HazardPattern {
|
||||
p := GetBuiltinHazardPatterns()
|
||||
p = append(p, GetExtendedHazardPatterns()...)
|
||||
p = append(p, GetPressHazardPatterns()...)
|
||||
p = append(p, GetCobotHazardPatterns()...)
|
||||
p = append(p, GetOperationalHazardPatterns()...)
|
||||
p = append(p, GetDGUVExtendedPatterns()...)
|
||||
p = append(p, GetExtendedHazardPatterns2()...)
|
||||
p = append(p, GetElevatorPatterns()...)
|
||||
p = append(p, GetAGVAgriPatterns()...)
|
||||
p = append(p, GetFoodProcessingPatterns()...)
|
||||
p = append(p, GetPackagingPatterns()...)
|
||||
p = append(p, GetLaserPatterns()...)
|
||||
p = append(p, GetMedicalDevicePatterns()...)
|
||||
p = append(p, GetPressureEquipmentPatterns()...)
|
||||
p = append(p, GetConstructionPatterns()...)
|
||||
p = append(p, GetForestryConveyorPatterns()...)
|
||||
p = append(p, GetPlasticsMetalPatterns()...)
|
||||
p = append(p, GetWeldingGlassTextilePatterns()...)
|
||||
p = append(p, GetSpecificMachinePatterns()...)
|
||||
p = append(p, GetSpecificMachinePatterns2()...)
|
||||
p = append(p, GetCyberExtendedPatterns()...)
|
||||
p = append(p, GetCyberExtendedPatterns2()...)
|
||||
p = append(p, GetCyberExtendedPatterns3()...)
|
||||
p = append(p, GetWorkshopPatterns()...)
|
||||
p = append(p, GetMaintenanceExtPatterns()...)
|
||||
p = append(p, GetFinalPatternsA()...)
|
||||
p = append(p, GetFinalPatternsB()...)
|
||||
p = append(p, GetFinalPatternsC()...)
|
||||
p = append(p, GetFinalPatternsD()...)
|
||||
return p
|
||||
return collectAllPatterns()
|
||||
}
|
||||
|
||||
// extractPatternIDs scans a text for "HP" followed by digits and adds
|
||||
|
||||
@@ -36,21 +36,21 @@ func GetComponentLibrary() []ComponentLibraryEntry {
|
||||
{ID: "C003", NameDE: "Foerderband", NameEN: "Conveyor Belt", Category: "mechanical", DescriptionDE: "Endlosband zum Transport von Werkstuecken zwischen Arbeitsstationen.", TypicalHazardCategories: []string{"mechanical_hazard", "ergonomic"}, TypicalEnergySources: []string{"EN01", "EN02"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "rotating_part", "entanglement_risk"}, SortOrder: 3},
|
||||
{ID: "C004", NameDE: "Drehtisch", NameEN: "Rotary Table", Category: "mechanical", DescriptionDE: "Rotierender Arbeitstisch fuer Bearbeitungs- oder Montageprozesse.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN02"}, MapsToComponentType: "mechanical", Tags: []string{"rotating_part", "high_force"}, SortOrder: 4},
|
||||
{ID: "C005", NameDE: "Linearachse", NameEN: "Linear Axis", Category: "mechanical", DescriptionDE: "Linearfuehrung fuer praezise translatorische Bewegungen.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN01"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "crush_point"}, SortOrder: 5},
|
||||
{ID: "C006", NameDE: "Spindel", NameEN: "Spindle", Category: "mechanical", DescriptionDE: "Hochdrehende Spindel fuer Fräs-, Bohr- oder Schleifoperationen.", TypicalHazardCategories: []string{"mechanical_hazard", "noise_vibration"}, TypicalEnergySources: []string{"EN02"}, MapsToComponentType: "mechanical", Tags: []string{"rotating_part", "high_speed", "cutting_part"}, SortOrder: 6},
|
||||
{ID: "C006", NameDE: "Spindel", NameEN: "Spindle", Category: "mechanical", DescriptionDE: "Hochdrehende Spindel fuer Fräs-, Bohr- oder Schleifoperationen.", TypicalHazardCategories: []string{"mechanical_hazard", "noise_vibration"}, TypicalEnergySources: []string{"EN02"}, MapsToComponentType: "mechanical", Tags: []string{"rotating_part", "high_speed", "cutting_part", "noise_source"}, SortOrder: 6},
|
||||
{ID: "C007", NameDE: "Saegeblatt", NameEN: "Saw Blade", Category: "mechanical", DescriptionDE: "Rotierendes oder oszillierendes Schneidwerkzeug.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN02"}, MapsToComponentType: "mechanical", Tags: []string{"cutting_part", "rotating_part", "high_speed"}, SortOrder: 7},
|
||||
{ID: "C008", NameDE: "Pressenstoessel", NameEN: "Press Ram", Category: "mechanical", DescriptionDE: "Auf- und abfahrender Stoessel einer Presse zum Umformen.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN01", "EN05"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "high_force", "crush_point"}, SortOrder: 8},
|
||||
{ID: "C009", NameDE: "Walze", NameEN: "Roller", Category: "mechanical", DescriptionDE: "Zylindrische Walze zum Foerdern, Pressen oder Kalandrieren.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN02"}, MapsToComponentType: "mechanical", Tags: []string{"rotating_part", "entanglement_risk", "pinch_point"}, SortOrder: 9},
|
||||
{ID: "C010", NameDE: "Kettenantrieb", NameEN: "Chain Drive", Category: "mechanical", DescriptionDE: "Kette und Kettenrad zur Kraftuebertragung.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN01"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "entanglement_risk"}, SortOrder: 10},
|
||||
{ID: "C011", NameDE: "Zahnradgetriebe", NameEN: "Gear Transmission", Category: "mechanical", DescriptionDE: "Zahnradpaar oder -satz zur Drehzahl-/Drehmomentanpassung.", TypicalHazardCategories: []string{"mechanical_hazard", "noise_vibration"}, TypicalEnergySources: []string{"EN02"}, MapsToComponentType: "mechanical", Tags: []string{"rotating_part", "pinch_point"}, SortOrder: 11},
|
||||
{ID: "C011", NameDE: "Zahnradgetriebe", NameEN: "Gear Transmission", Category: "mechanical", DescriptionDE: "Zahnradpaar oder -satz zur Drehzahl-/Drehmomentanpassung.", TypicalHazardCategories: []string{"mechanical_hazard", "noise_vibration"}, TypicalEnergySources: []string{"EN02"}, MapsToComponentType: "mechanical", Tags: []string{"rotating_part", "pinch_point", "noise_source"}, SortOrder: 11},
|
||||
{ID: "C012", NameDE: "Kupplung", NameEN: "Clutch", Category: "mechanical", DescriptionDE: "Mechanische Kupplung zur An-/Abkopplung von Antriebsstraengen.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN02"}, MapsToComponentType: "mechanical", Tags: []string{"rotating_part"}, SortOrder: 12},
|
||||
{ID: "C013", NameDE: "Bremse", NameEN: "Brake", Category: "mechanical", DescriptionDE: "Mechanische oder elektromagnetische Bremse zum Stillsetzen von Antrieben.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN01"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "stored_energy"}, SortOrder: 13},
|
||||
{ID: "C014", NameDE: "Hubwerk", NameEN: "Hoist", Category: "mechanical", DescriptionDE: "Hebezeug zum vertikalen Bewegen von Lasten.", TypicalHazardCategories: []string{"mechanical_hazard", "ergonomic"}, TypicalEnergySources: []string{"EN01", "EN03"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "high_force", "gravity_risk"}, SortOrder: 14},
|
||||
{ID: "C014", NameDE: "Hubwerk", NameEN: "Hoist", Category: "mechanical", DescriptionDE: "Hebezeug zum vertikalen Bewegen von Lasten.", TypicalHazardCategories: []string{"mechanical_hazard", "ergonomic"}, TypicalEnergySources: []string{"EN01", "EN03", "EN04"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "high_force", "gravity_risk", "crush_point", "person_under_load"}, SortOrder: 14},
|
||||
{ID: "C015", NameDE: "Werkzeugwechsler", NameEN: "Tool Changer", Category: "mechanical", DescriptionDE: "Automatischer Werkzeugwechsler fuer CNC-Maschinen.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN01", "EN05"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "pinch_point"}, SortOrder: 15},
|
||||
{ID: "C016", NameDE: "Schweisskopf", NameEN: "Welding Head", Category: "mechanical", DescriptionDE: "Schweisskopf fuer MIG/MAG, WIG oder Laserschweissen.", TypicalHazardCategories: []string{"mechanical_hazard", "thermal_hazard", "electrical_hazard"}, TypicalEnergySources: []string{"EN03", "EN07"}, MapsToComponentType: "mechanical", Tags: []string{"high_temperature", "radiation_risk"}, SortOrder: 16},
|
||||
{ID: "C017", NameDE: "Schraubstation", NameEN: "Screwdriving Station", Category: "mechanical", DescriptionDE: "Automatische Schraubeinheit fuer Montageprozesse.", TypicalHazardCategories: []string{"mechanical_hazard", "noise_vibration"}, TypicalEnergySources: []string{"EN02"}, MapsToComponentType: "mechanical", Tags: []string{"rotating_part"}, SortOrder: 17},
|
||||
{ID: "C017", NameDE: "Schraubstation", NameEN: "Screwdriving Station", Category: "mechanical", DescriptionDE: "Automatische Schraubeinheit fuer Montageprozesse.", TypicalHazardCategories: []string{"mechanical_hazard", "noise_vibration"}, TypicalEnergySources: []string{"EN02"}, MapsToComponentType: "mechanical", Tags: []string{"rotating_part", "noise_source"}, SortOrder: 17},
|
||||
{ID: "C018", NameDE: "Stanzen-Werkzeug", NameEN: "Punching Tool", Category: "mechanical", DescriptionDE: "Stanzwerkzeug zum Ausschneiden von Formen aus Blech oder Folie.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN01"}, MapsToComponentType: "mechanical", Tags: []string{"cutting_part", "high_force", "crush_point"}, SortOrder: 18},
|
||||
{ID: "C019", NameDE: "Biegewerkzeug", NameEN: "Bending Tool", Category: "mechanical", DescriptionDE: "Werkzeug zum Biegen von Blech oder Profilen.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN01"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "high_force", "crush_point"}, SortOrder: 19},
|
||||
{ID: "C020", NameDE: "Vibrationsfoerderer", NameEN: "Vibratory Feeder", Category: "mechanical", DescriptionDE: "Schwingfoerderer zum Sortieren und Zufuehren von Kleinteilen.", TypicalHazardCategories: []string{"mechanical_hazard", "noise_vibration"}, TypicalEnergySources: []string{"EN01"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "vibration_source"}, SortOrder: 20},
|
||||
{ID: "C020", NameDE: "Vibrationsfoerderer", NameEN: "Vibratory Feeder", Category: "mechanical", DescriptionDE: "Schwingfoerderer zum Sortieren und Zufuehren von Kleinteilen.", TypicalHazardCategories: []string{"mechanical_hazard", "noise_vibration"}, TypicalEnergySources: []string{"EN01"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "vibration_source", "noise_source"}, SortOrder: 20},
|
||||
|
||||
// ── Category: structural (C021-C030) ────────────────────────────────────
|
||||
{ID: "C021", NameDE: "Maschinenrahmen", NameEN: "Machine Frame", Category: "structural", DescriptionDE: "Tragender Rahmen als Grundstruktur der Maschine.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{}, MapsToComponentType: "mechanical", Tags: []string{"structural_part"}, SortOrder: 21},
|
||||
@@ -65,19 +65,19 @@ func GetComponentLibrary() []ComponentLibraryEntry {
|
||||
{ID: "C030", NameDE: "Plattform/Buehne", NameEN: "Platform/Walkway", Category: "structural", DescriptionDE: "Begehbare Plattform fuer Bedienung oder Wartung in der Hoehe.", TypicalHazardCategories: []string{"ergonomic", "mechanical_hazard"}, TypicalEnergySources: []string{"EN03"}, MapsToComponentType: "mechanical", Tags: []string{"structural_part", "gravity_risk"}, SortOrder: 30},
|
||||
|
||||
// ── Category: drive (C031-C040) ─────────────────────────────────────────
|
||||
{ID: "C031", NameDE: "Elektromotor (Drehstrom)", NameEN: "AC Motor", Category: "drive", DescriptionDE: "Drehstrom-Asynchronmotor als Hauptantrieb.", TypicalHazardCategories: []string{"electrical_hazard", "mechanical_hazard", "noise_vibration"}, TypicalEnergySources: []string{"EN02", "EN04"}, MapsToComponentType: "electrical", Tags: []string{"rotating_part", "high_voltage", "high_force"}, SortOrder: 31},
|
||||
{ID: "C032", NameDE: "Servomotor", NameEN: "Servo Motor", Category: "drive", DescriptionDE: "Hochdynamischer Servomotor fuer praezise Positionierung.", TypicalHazardCategories: []string{"electrical_hazard", "mechanical_hazard"}, TypicalEnergySources: []string{"EN02", "EN04"}, MapsToComponentType: "electrical", Tags: []string{"rotating_part", "high_speed"}, SortOrder: 32},
|
||||
{ID: "C033", NameDE: "Schrittmotor", NameEN: "Stepper Motor", Category: "drive", DescriptionDE: "Schrittmotor fuer inkrementelle Positionierung.", TypicalHazardCategories: []string{"electrical_hazard"}, TypicalEnergySources: []string{"EN02", "EN04"}, MapsToComponentType: "electrical", Tags: []string{"rotating_part"}, SortOrder: 33},
|
||||
{ID: "C034", NameDE: "Frequenzumrichter", NameEN: "Frequency Converter", Category: "drive", DescriptionDE: "Frequenzumrichter zur stufenlosen Drehzahlregelung.", TypicalHazardCategories: []string{"electrical_hazard", "emc_hazard"}, TypicalEnergySources: []string{"EN04"}, MapsToComponentType: "electrical", Tags: []string{"high_voltage", "stored_energy"}, SortOrder: 34},
|
||||
{ID: "C035", NameDE: "Getriebemotor", NameEN: "Gear Motor", Category: "drive", DescriptionDE: "Motor mit integriertem Getriebe fuer hohes Drehmoment bei niedriger Drehzahl.", TypicalHazardCategories: []string{"mechanical_hazard", "electrical_hazard"}, TypicalEnergySources: []string{"EN02", "EN04"}, MapsToComponentType: "electrical", Tags: []string{"rotating_part", "high_force"}, SortOrder: 35},
|
||||
{ID: "C036", NameDE: "Linearmotor", NameEN: "Linear Motor", Category: "drive", DescriptionDE: "Elektromagnetischer Direktantrieb fuer lineare Bewegung.", TypicalHazardCategories: []string{"electrical_hazard", "mechanical_hazard"}, TypicalEnergySources: []string{"EN01", "EN04"}, MapsToComponentType: "electrical", Tags: []string{"moving_part", "high_speed"}, SortOrder: 36},
|
||||
{ID: "C037", NameDE: "Torque-Motor", NameEN: "Torque Motor", Category: "drive", DescriptionDE: "Direktantriebsmotor fuer hohe Drehmomente ohne Getriebe.", TypicalHazardCategories: []string{"electrical_hazard", "mechanical_hazard"}, TypicalEnergySources: []string{"EN02", "EN04"}, MapsToComponentType: "electrical", Tags: []string{"rotating_part", "high_force"}, SortOrder: 37},
|
||||
{ID: "C038", NameDE: "Elektrischer Stellantrieb", NameEN: "Electric Actuator", Category: "drive", DescriptionDE: "Elektrischer Antrieb fuer Ventile, Klappen oder Schieber.", TypicalHazardCategories: []string{"electrical_hazard"}, TypicalEnergySources: []string{"EN01", "EN04"}, MapsToComponentType: "actuator", Tags: []string{"moving_part"}, SortOrder: 38},
|
||||
{ID: "C031", NameDE: "Elektromotor (Drehstrom)", NameEN: "AC Motor", Category: "drive", DescriptionDE: "Drehstrom-Asynchronmotor als Hauptantrieb.", TypicalHazardCategories: []string{"electrical_hazard", "mechanical_hazard", "noise_vibration"}, TypicalEnergySources: []string{"EN02", "EN04"}, MapsToComponentType: "electrical", Tags: []string{"rotating_part", "high_voltage", "high_force", "noise_source", "electrical_part"}, SortOrder: 31},
|
||||
{ID: "C032", NameDE: "Servomotor", NameEN: "Servo Motor", Category: "drive", DescriptionDE: "Hochdynamischer Servomotor fuer praezise Positionierung.", TypicalHazardCategories: []string{"electrical_hazard", "mechanical_hazard"}, TypicalEnergySources: []string{"EN02", "EN04"}, MapsToComponentType: "electrical", Tags: []string{"rotating_part", "high_speed", "electrical_part"}, SortOrder: 32},
|
||||
{ID: "C033", NameDE: "Schrittmotor", NameEN: "Stepper Motor", Category: "drive", DescriptionDE: "Schrittmotor fuer inkrementelle Positionierung.", TypicalHazardCategories: []string{"electrical_hazard"}, TypicalEnergySources: []string{"EN02", "EN04"}, MapsToComponentType: "electrical", Tags: []string{"rotating_part", "electrical_part"}, SortOrder: 33},
|
||||
{ID: "C034", NameDE: "Frequenzumrichter", NameEN: "Frequency Converter", Category: "drive", DescriptionDE: "Frequenzumrichter zur stufenlosen Drehzahlregelung.", TypicalHazardCategories: []string{"electrical_hazard", "emc_hazard"}, TypicalEnergySources: []string{"EN04"}, MapsToComponentType: "electrical", Tags: []string{"high_voltage", "stored_energy", "electrical_part", "electromagnetic"}, SortOrder: 34},
|
||||
{ID: "C035", NameDE: "Getriebemotor", NameEN: "Gear Motor", Category: "drive", DescriptionDE: "Motor mit integriertem Getriebe fuer hohes Drehmoment bei niedriger Drehzahl.", TypicalHazardCategories: []string{"mechanical_hazard", "electrical_hazard"}, TypicalEnergySources: []string{"EN02", "EN04"}, MapsToComponentType: "electrical", Tags: []string{"rotating_part", "high_force", "electrical_part"}, SortOrder: 35},
|
||||
{ID: "C036", NameDE: "Linearmotor", NameEN: "Linear Motor", Category: "drive", DescriptionDE: "Elektromagnetischer Direktantrieb fuer lineare Bewegung.", TypicalHazardCategories: []string{"electrical_hazard", "mechanical_hazard"}, TypicalEnergySources: []string{"EN01", "EN04"}, MapsToComponentType: "electrical", Tags: []string{"moving_part", "high_speed", "electrical_part"}, SortOrder: 36},
|
||||
{ID: "C037", NameDE: "Torque-Motor", NameEN: "Torque Motor", Category: "drive", DescriptionDE: "Direktantriebsmotor fuer hohe Drehmomente ohne Getriebe.", TypicalHazardCategories: []string{"electrical_hazard", "mechanical_hazard"}, TypicalEnergySources: []string{"EN02", "EN04"}, MapsToComponentType: "electrical", Tags: []string{"rotating_part", "high_force", "electrical_part"}, SortOrder: 37},
|
||||
{ID: "C038", NameDE: "Elektrischer Stellantrieb", NameEN: "Electric Actuator", Category: "drive", DescriptionDE: "Elektrischer Antrieb fuer Ventile, Klappen oder Schieber.", TypicalHazardCategories: []string{"electrical_hazard"}, TypicalEnergySources: []string{"EN01", "EN04"}, MapsToComponentType: "actuator", Tags: []string{"moving_part", "electrical_part"}, SortOrder: 38},
|
||||
{ID: "C039", NameDE: "Spindelantrieb", NameEN: "Spindle Drive", Category: "drive", DescriptionDE: "Kugelgewindetrieb fuer praezise Linearbewegung.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN01"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "crush_point"}, SortOrder: 39},
|
||||
{ID: "C040", NameDE: "Riemenantrieb", NameEN: "Belt Drive", Category: "drive", DescriptionDE: "Riemen und Riemenscheiben zur Kraftuebertragung.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN02"}, MapsToComponentType: "mechanical", Tags: []string{"rotating_part", "entanglement_risk"}, SortOrder: 40},
|
||||
|
||||
// ── Category: hydraulic (C041-C050) ─────────────────────────────────────
|
||||
{ID: "C041", NameDE: "Hydraulikpumpe", NameEN: "Hydraulic Pump", Category: "hydraulic", DescriptionDE: "Pumpe zur Erzeugung des hydraulischen Drucks im System.", TypicalHazardCategories: []string{"pneumatic_hydraulic", "noise_vibration"}, TypicalEnergySources: []string{"EN05"}, MapsToComponentType: "actuator", Tags: []string{"hydraulic_part", "high_pressure"}, SortOrder: 41},
|
||||
{ID: "C041", NameDE: "Hydraulikpumpe", NameEN: "Hydraulic Pump", Category: "hydraulic", DescriptionDE: "Pumpe zur Erzeugung des hydraulischen Drucks im System.", TypicalHazardCategories: []string{"pneumatic_hydraulic", "noise_vibration"}, TypicalEnergySources: []string{"EN05"}, MapsToComponentType: "actuator", Tags: []string{"hydraulic_part", "high_pressure", "noise_source"}, SortOrder: 41},
|
||||
{ID: "C042", NameDE: "Hydraulikzylinder", NameEN: "Hydraulic Cylinder", Category: "hydraulic", DescriptionDE: "Linearaktuator zur Erzeugung hoher Kraefte.", TypicalHazardCategories: []string{"pneumatic_hydraulic", "mechanical_hazard"}, TypicalEnergySources: []string{"EN05"}, MapsToComponentType: "actuator", Tags: []string{"hydraulic_part", "moving_part", "high_force", "high_pressure"}, SortOrder: 42},
|
||||
{ID: "C043", NameDE: "Hydraulikventil", NameEN: "Hydraulic Valve", Category: "hydraulic", DescriptionDE: "Steuer- oder Regelventil im Hydraulikkreislauf.", TypicalHazardCategories: []string{"pneumatic_hydraulic"}, TypicalEnergySources: []string{"EN05"}, MapsToComponentType: "actuator", Tags: []string{"hydraulic_part", "high_pressure"}, SortOrder: 43},
|
||||
{ID: "C044", NameDE: "Hydraulikspeicher", NameEN: "Hydraulic Accumulator", Category: "hydraulic", DescriptionDE: "Druckspeicher zur Pufferung von Druckspitzen.", TypicalHazardCategories: []string{"pneumatic_hydraulic"}, TypicalEnergySources: []string{"EN05"}, MapsToComponentType: "actuator", Tags: []string{"hydraulic_part", "stored_energy", "high_pressure"}, SortOrder: 44},
|
||||
@@ -117,33 +117,33 @@ func GetComponentLibrary() []ComponentLibraryEntry {
|
||||
{ID: "C072", NameDE: "Sicherheits-SPS", NameEN: "Safety PLC", Category: "control", DescriptionDE: "Redundante Sicherheitssteuerung bis SIL 3 / PL e.", TypicalHazardCategories: []string{"safety_function_failure", "software_fault"}, TypicalEnergySources: []string{}, MapsToComponentType: "controller", Tags: []string{"has_software", "programmable", "safety_device"}, SortOrder: 72},
|
||||
{ID: "C073", NameDE: "HMI (Bedienterminal)", NameEN: "HMI (Human Machine Interface)", Category: "control", DescriptionDE: "Bedienpanel mit Touchscreen zur Maschinensteuerung.", TypicalHazardCategories: []string{"hmi_error", "mode_confusion"}, TypicalEnergySources: []string{}, MapsToComponentType: "hmi", Tags: []string{"has_software", "user_interface"}, SortOrder: 73},
|
||||
{ID: "C074", NameDE: "Industrierechner (IPC)", NameEN: "Industrial PC", Category: "control", DescriptionDE: "Industrie-PC fuer komplexe Steuerungs- und Datenverarbeitungsaufgaben.", TypicalHazardCategories: []string{"software_fault", "configuration_error"}, TypicalEnergySources: []string{}, MapsToComponentType: "controller", Tags: []string{"has_software", "programmable", "networked"}, SortOrder: 74},
|
||||
{ID: "C075", NameDE: "Motion Controller", NameEN: "Motion Controller", Category: "control", DescriptionDE: "Achscontroller fuer synchronisierte Mehrachsbewegungen.", TypicalHazardCategories: []string{"software_fault", "mechanical_hazard"}, TypicalEnergySources: []string{}, MapsToComponentType: "controller", Tags: []string{"has_software", "programmable"}, SortOrder: 75},
|
||||
{ID: "C075", NameDE: "Motion Controller", NameEN: "Motion Controller", Category: "control", DescriptionDE: "Achscontroller fuer synchronisierte Mehrachsbewegungen.", TypicalHazardCategories: []string{"software_fault", "mechanical_hazard"}, TypicalEnergySources: []string{}, MapsToComponentType: "controller", Tags: []string{"has_software", "programmable", "moving_part"}, SortOrder: 75},
|
||||
{ID: "C076", NameDE: "Sicherheitsrelais", NameEN: "Safety Relay", Category: "control", DescriptionDE: "Sicherheitsschaltgeraet fuer Not-Halt, Schutztuer etc.", TypicalHazardCategories: []string{"safety_function_failure"}, TypicalEnergySources: []string{}, MapsToComponentType: "controller", Tags: []string{"safety_device"}, SortOrder: 76},
|
||||
{ID: "C077", NameDE: "Antriebsregler", NameEN: "Drive Controller", Category: "control", DescriptionDE: "Intelligenter Antriebsregler mit integrierten Sicherheitsfunktionen.", TypicalHazardCategories: []string{"software_fault", "electrical_hazard"}, TypicalEnergySources: []string{"EN04"}, MapsToComponentType: "controller", Tags: []string{"has_software", "programmable"}, SortOrder: 77},
|
||||
{ID: "C077", NameDE: "Antriebsregler", NameEN: "Drive Controller", Category: "control", DescriptionDE: "Intelligenter Antriebsregler mit integrierten Sicherheitsfunktionen.", TypicalHazardCategories: []string{"software_fault", "electrical_hazard"}, TypicalEnergySources: []string{"EN04"}, MapsToComponentType: "controller", Tags: []string{"has_software", "programmable", "electrical_part"}, SortOrder: 77},
|
||||
{ID: "C078", NameDE: "Remote I/O", NameEN: "Remote I/O Module", Category: "control", DescriptionDE: "Dezentrales Ein-/Ausgangsmodul im Feldbus.", TypicalHazardCategories: []string{"communication_failure"}, TypicalEnergySources: []string{}, MapsToComponentType: "controller", Tags: []string{"networked"}, SortOrder: 78},
|
||||
{ID: "C079", NameDE: "Bedienpult", NameEN: "Control Desk", Category: "control", DescriptionDE: "Zentrales Bedienpult mit Tastern, Schaltern und Anzeigen.", TypicalHazardCategories: []string{"hmi_error", "mode_confusion"}, TypicalEnergySources: []string{}, MapsToComponentType: "hmi", Tags: []string{"user_interface"}, SortOrder: 79},
|
||||
{ID: "C080", NameDE: "Datenschreiber/Logger", NameEN: "Data Logger", Category: "control", DescriptionDE: "Geraet zur Aufzeichnung von Prozessparametern.", TypicalHazardCategories: []string{"logging_audit_failure"}, TypicalEnergySources: []string{}, MapsToComponentType: "controller", Tags: []string{"has_software"}, SortOrder: 80},
|
||||
|
||||
// ── Category: sensor (C081-C090) ────────────────────────────────────────
|
||||
{ID: "C081", NameDE: "Positionssensor", NameEN: "Position Sensor", Category: "sensor", DescriptionDE: "Induktiver, kapazitiver oder optischer Positionssensor.", TypicalHazardCategories: []string{"sensor_spoofing"}, TypicalEnergySources: []string{}, MapsToComponentType: "sensor", Tags: []string{"sensor_part"}, SortOrder: 81},
|
||||
{ID: "C082", NameDE: "Kamerasystem", NameEN: "Camera System", Category: "sensor", DescriptionDE: "Industriekamera fuer Bildverarbeitung und Qualitaetskontrolle.", TypicalHazardCategories: []string{"sensor_spoofing", "false_classification"}, TypicalEnergySources: []string{}, MapsToComponentType: "sensor", Tags: []string{"sensor_part", "networked"}, SortOrder: 82},
|
||||
{ID: "C083", NameDE: "Kraftsensor", NameEN: "Force Sensor", Category: "sensor", DescriptionDE: "Dehnungsmessstreifen oder piezoelektrischer Kraftsensor.", TypicalHazardCategories: []string{"sensor_spoofing"}, TypicalEnergySources: []string{}, MapsToComponentType: "sensor", Tags: []string{"sensor_part"}, SortOrder: 83},
|
||||
{ID: "C084", NameDE: "Temperatursensor", NameEN: "Temperature Sensor", Category: "sensor", DescriptionDE: "Thermocouple oder PT100 zur Temperaturueberwachung.", TypicalHazardCategories: []string{"sensor_spoofing"}, TypicalEnergySources: []string{}, MapsToComponentType: "sensor", Tags: []string{"sensor_part"}, SortOrder: 84},
|
||||
{ID: "C085", NameDE: "Drucksensor", NameEN: "Pressure Sensor", Category: "sensor", DescriptionDE: "Sensor zur Ueberwachung von Druck in Hydraulik- oder Pneumatiksystemen.", TypicalHazardCategories: []string{"sensor_spoofing"}, TypicalEnergySources: []string{}, MapsToComponentType: "sensor", Tags: []string{"sensor_part"}, SortOrder: 85},
|
||||
{ID: "C086", NameDE: "Drehgeber/Encoder", NameEN: "Rotary Encoder", Category: "sensor", DescriptionDE: "Absolut- oder Inkrementaldrehgeber zur Winkel-/Positionsmessung.", TypicalHazardCategories: []string{"sensor_spoofing"}, TypicalEnergySources: []string{}, MapsToComponentType: "sensor", Tags: []string{"sensor_part"}, SortOrder: 86},
|
||||
{ID: "C087", NameDE: "Laserscanner", NameEN: "Laser Scanner", Category: "sensor", DescriptionDE: "Sicherheits-Laserscanner zur Ueberwachung von Schutzzonen.", TypicalHazardCategories: []string{"sensor_spoofing", "safety_function_failure"}, TypicalEnergySources: []string{}, MapsToComponentType: "sensor", Tags: []string{"sensor_part", "safety_device"}, SortOrder: 87},
|
||||
{ID: "C088", NameDE: "Beschleunigungssensor", NameEN: "Accelerometer", Category: "sensor", DescriptionDE: "Sensor zur Vibrations- und Beschleunigungsmessung.", TypicalHazardCategories: []string{"sensor_spoofing"}, TypicalEnergySources: []string{}, MapsToComponentType: "sensor", Tags: []string{"sensor_part"}, SortOrder: 88},
|
||||
{ID: "C089", NameDE: "Durchflusssensor", NameEN: "Flow Sensor", Category: "sensor", DescriptionDE: "Sensor zur Ueberwachung des Volumenstrom.", TypicalHazardCategories: []string{"sensor_spoofing"}, TypicalEnergySources: []string{}, MapsToComponentType: "sensor", Tags: []string{"sensor_part"}, SortOrder: 89},
|
||||
{ID: "C090", NameDE: "Fuellstandsensor", NameEN: "Level Sensor", Category: "sensor", DescriptionDE: "Sensor zur Ueberwachung des Fuellstands in Tanks und Behaeltern.", TypicalHazardCategories: []string{"sensor_spoofing"}, TypicalEnergySources: []string{}, MapsToComponentType: "sensor", Tags: []string{"sensor_part"}, SortOrder: 90},
|
||||
{ID: "C081", NameDE: "Positionssensor", NameEN: "Position Sensor", Category: "sensor", DescriptionDE: "Induktiver, kapazitiver oder optischer Positionssensor.", TypicalHazardCategories: []string{"sensor_spoofing"}, TypicalEnergySources: []string{}, MapsToComponentType: "sensor", Tags: []string{"sensor_part", "cyber"}, SortOrder: 81},
|
||||
{ID: "C082", NameDE: "Kamerasystem", NameEN: "Camera System", Category: "sensor", DescriptionDE: "Industriekamera fuer Bildverarbeitung und Qualitaetskontrolle.", TypicalHazardCategories: []string{"sensor_spoofing", "false_classification"}, TypicalEnergySources: []string{}, MapsToComponentType: "sensor", Tags: []string{"sensor_part", "networked", "cyber", "has_ai"}, SortOrder: 82},
|
||||
{ID: "C083", NameDE: "Kraftsensor", NameEN: "Force Sensor", Category: "sensor", DescriptionDE: "Dehnungsmessstreifen oder piezoelektrischer Kraftsensor.", TypicalHazardCategories: []string{"sensor_spoofing"}, TypicalEnergySources: []string{}, MapsToComponentType: "sensor", Tags: []string{"sensor_part", "cyber"}, SortOrder: 83},
|
||||
{ID: "C084", NameDE: "Temperatursensor", NameEN: "Temperature Sensor", Category: "sensor", DescriptionDE: "Thermocouple oder PT100 zur Temperaturueberwachung.", TypicalHazardCategories: []string{"sensor_spoofing"}, TypicalEnergySources: []string{}, MapsToComponentType: "sensor", Tags: []string{"sensor_part", "cyber"}, SortOrder: 84},
|
||||
{ID: "C085", NameDE: "Drucksensor", NameEN: "Pressure Sensor", Category: "sensor", DescriptionDE: "Sensor zur Ueberwachung von Druck in Hydraulik- oder Pneumatiksystemen.", TypicalHazardCategories: []string{"sensor_spoofing"}, TypicalEnergySources: []string{}, MapsToComponentType: "sensor", Tags: []string{"sensor_part", "cyber"}, SortOrder: 85},
|
||||
{ID: "C086", NameDE: "Drehgeber/Encoder", NameEN: "Rotary Encoder", Category: "sensor", DescriptionDE: "Absolut- oder Inkrementaldrehgeber zur Winkel-/Positionsmessung.", TypicalHazardCategories: []string{"sensor_spoofing"}, TypicalEnergySources: []string{}, MapsToComponentType: "sensor", Tags: []string{"sensor_part", "cyber"}, SortOrder: 86},
|
||||
{ID: "C087", NameDE: "Laserscanner", NameEN: "Laser Scanner", Category: "sensor", DescriptionDE: "Sicherheits-Laserscanner zur Ueberwachung von Schutzzonen.", TypicalHazardCategories: []string{"sensor_spoofing", "safety_function_failure"}, TypicalEnergySources: []string{}, MapsToComponentType: "sensor", Tags: []string{"sensor_part", "safety_device", "cyber"}, SortOrder: 87},
|
||||
{ID: "C088", NameDE: "Beschleunigungssensor", NameEN: "Accelerometer", Category: "sensor", DescriptionDE: "Sensor zur Vibrations- und Beschleunigungsmessung.", TypicalHazardCategories: []string{"sensor_spoofing"}, TypicalEnergySources: []string{}, MapsToComponentType: "sensor", Tags: []string{"sensor_part", "cyber"}, SortOrder: 88},
|
||||
{ID: "C089", NameDE: "Durchflusssensor", NameEN: "Flow Sensor", Category: "sensor", DescriptionDE: "Sensor zur Ueberwachung des Volumenstrom.", TypicalHazardCategories: []string{"sensor_spoofing"}, TypicalEnergySources: []string{}, MapsToComponentType: "sensor", Tags: []string{"sensor_part", "cyber"}, SortOrder: 89},
|
||||
{ID: "C090", NameDE: "Fuellstandsensor", NameEN: "Level Sensor", Category: "sensor", DescriptionDE: "Sensor zur Ueberwachung des Fuellstands in Tanks und Behaeltern.", TypicalHazardCategories: []string{"sensor_spoofing"}, TypicalEnergySources: []string{}, MapsToComponentType: "sensor", Tags: []string{"sensor_part", "cyber"}, SortOrder: 90},
|
||||
|
||||
// ── Category: actuator (C091-C100) ──────────────────────────────────────
|
||||
{ID: "C091", NameDE: "Magnetventil", NameEN: "Solenoid Valve", Category: "actuator", DescriptionDE: "Elektromagnetisch betaetigtes Ventil fuer Pneumatik oder Hydraulik.", TypicalHazardCategories: []string{"pneumatic_hydraulic"}, TypicalEnergySources: []string{"EN05", "EN06"}, MapsToComponentType: "actuator", Tags: []string{"actuator_part"}, SortOrder: 91},
|
||||
{ID: "C091", NameDE: "Magnetventil", NameEN: "Solenoid Valve", Category: "actuator", DescriptionDE: "Elektromagnetisch betaetigtes Ventil fuer Pneumatik oder Hydraulik.", TypicalHazardCategories: []string{"pneumatic_hydraulic"}, TypicalEnergySources: []string{"EN05", "EN06"}, MapsToComponentType: "actuator", Tags: []string{"actuator_part", "hydraulic_part", "pneumatic_part", "high_pressure"}, SortOrder: 91},
|
||||
{ID: "C092", NameDE: "Linearantrieb (elektrisch)", NameEN: "Electric Linear Actuator", Category: "actuator", DescriptionDE: "Elektrischer Linearantrieb fuer Positionieraufgaben.", TypicalHazardCategories: []string{"mechanical_hazard", "electrical_hazard"}, TypicalEnergySources: []string{"EN01", "EN04"}, MapsToComponentType: "actuator", Tags: []string{"actuator_part", "moving_part"}, SortOrder: 92},
|
||||
{ID: "C093", NameDE: "Proportionalventil", NameEN: "Proportional Valve", Category: "actuator", DescriptionDE: "Stetig regelbares Ventil fuer praezise Drucksteuerung.", TypicalHazardCategories: []string{"pneumatic_hydraulic"}, TypicalEnergySources: []string{"EN05", "EN06"}, MapsToComponentType: "actuator", Tags: []string{"actuator_part"}, SortOrder: 93},
|
||||
{ID: "C093", NameDE: "Proportionalventil", NameEN: "Proportional Valve", Category: "actuator", DescriptionDE: "Stetig regelbares Ventil fuer praezise Drucksteuerung.", TypicalHazardCategories: []string{"pneumatic_hydraulic"}, TypicalEnergySources: []string{"EN05", "EN06"}, MapsToComponentType: "actuator", Tags: []string{"actuator_part", "hydraulic_part", "pneumatic_part", "high_pressure"}, SortOrder: 93},
|
||||
{ID: "C094", NameDE: "Heizelement", NameEN: "Heating Element", Category: "actuator", DescriptionDE: "Elektrisches Heizelement fuer Temperierung von Werkzeugen oder Medien.", TypicalHazardCategories: []string{"thermal_hazard", "electrical_hazard"}, TypicalEnergySources: []string{"EN07"}, MapsToComponentType: "actuator", Tags: []string{"actuator_part", "high_temperature"}, SortOrder: 94},
|
||||
{ID: "C095", NameDE: "Kuehlaggregat", NameEN: "Cooling Unit", Category: "actuator", DescriptionDE: "Kuehlanlage fuer Prozesse oder Schaltschraenke.", TypicalHazardCategories: []string{"thermal_hazard"}, TypicalEnergySources: []string{"EN07"}, MapsToComponentType: "actuator", Tags: []string{"actuator_part"}, SortOrder: 95},
|
||||
{ID: "C096", NameDE: "Luefter/Geblaese", NameEN: "Fan/Blower", Category: "actuator", DescriptionDE: "Luefter zur Kuehlung oder Absaugung.", TypicalHazardCategories: []string{"mechanical_hazard", "noise_vibration"}, TypicalEnergySources: []string{"EN02"}, MapsToComponentType: "actuator", Tags: []string{"actuator_part", "rotating_part"}, SortOrder: 96},
|
||||
{ID: "C097", NameDE: "Dosierpumpe", NameEN: "Dosing Pump", Category: "actuator", DescriptionDE: "Praezisionspumpe zur Dosierung von Fluessigkeiten oder Klebstoffen.", TypicalHazardCategories: []string{"pneumatic_hydraulic", "material_environmental"}, TypicalEnergySources: []string{"EN05"}, MapsToComponentType: "actuator", Tags: []string{"actuator_part"}, SortOrder: 97},
|
||||
{ID: "C096", NameDE: "Luefter/Geblaese", NameEN: "Fan/Blower", Category: "actuator", DescriptionDE: "Luefter zur Kuehlung oder Absaugung.", TypicalHazardCategories: []string{"mechanical_hazard", "noise_vibration"}, TypicalEnergySources: []string{"EN02"}, MapsToComponentType: "actuator", Tags: []string{"actuator_part", "rotating_part", "noise_source"}, SortOrder: 96},
|
||||
{ID: "C097", NameDE: "Dosierpumpe", NameEN: "Dosing Pump", Category: "actuator", DescriptionDE: "Praezisionspumpe zur Dosierung von Fluessigkeiten oder Klebstoffen.", TypicalHazardCategories: []string{"pneumatic_hydraulic", "material_environmental"}, TypicalEnergySources: []string{"EN05"}, MapsToComponentType: "actuator", Tags: []string{"actuator_part", "hydraulic_part", "chemical_risk"}, SortOrder: 97},
|
||||
{ID: "C098", NameDE: "Elektromagnet", NameEN: "Electromagnet", Category: "actuator", DescriptionDE: "Elektromagnet fuer Halten, Spannen oder Foerdern.", TypicalHazardCategories: []string{"electrical_hazard", "emc_hazard"}, TypicalEnergySources: []string{"EN04"}, MapsToComponentType: "actuator", Tags: []string{"actuator_part", "stored_energy"}, SortOrder: 98},
|
||||
{ID: "C099", NameDE: "Piezo-Aktuator", NameEN: "Piezo Actuator", Category: "actuator", DescriptionDE: "Piezoelektrischer Aktuator fuer hochpraezise Mikrobewegungen.", TypicalHazardCategories: []string{"electrical_hazard"}, TypicalEnergySources: []string{"EN04"}, MapsToComponentType: "actuator", Tags: []string{"actuator_part"}, SortOrder: 99},
|
||||
{ID: "C100", NameDE: "Spannvorrichtung", NameEN: "Clamping Device", Category: "actuator", DescriptionDE: "Mechanische, pneumatische oder hydraulische Spannvorrichtung.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN01", "EN05", "EN06"}, MapsToComponentType: "actuator", Tags: []string{"actuator_part", "clamping_part", "pinch_point"}, SortOrder: 100},
|
||||
@@ -161,15 +161,15 @@ func GetComponentLibrary() []ComponentLibraryEntry {
|
||||
{ID: "C110", NameDE: "Zustimmtaster", NameEN: "Enabling Device", Category: "safety", DescriptionDE: "Dreistufiger Zustimmtaster fuer den Einrichtbetrieb.", TypicalHazardCategories: []string{"safety_function_failure"}, TypicalEnergySources: []string{}, MapsToComponentType: "controller", Tags: []string{"safety_device"}, SortOrder: 110},
|
||||
|
||||
// ── Category: it_network (C111-C120) ────────────────────────────────────
|
||||
{ID: "C111", NameDE: "Industrie-Switch (managed)", NameEN: "Managed Industrial Switch", Category: "it_network", DescriptionDE: "Managed Ethernet Switch fuer das Maschinennetzwerk.", TypicalHazardCategories: []string{"communication_failure", "unauthorized_access"}, TypicalEnergySources: []string{}, MapsToComponentType: "network", Tags: []string{"networked", "it_component"}, SortOrder: 111},
|
||||
{ID: "C112", NameDE: "Industrie-Router", NameEN: "Industrial Router", Category: "it_network", DescriptionDE: "Router zur Segmentierung und Absicherung des Maschinennetzwerks.", TypicalHazardCategories: []string{"communication_failure", "unauthorized_access"}, TypicalEnergySources: []string{}, MapsToComponentType: "network", Tags: []string{"networked", "it_component"}, SortOrder: 112},
|
||||
{ID: "C111", NameDE: "Industrie-Switch (managed)", NameEN: "Managed Industrial Switch", Category: "it_network", DescriptionDE: "Managed Ethernet Switch fuer das Maschinennetzwerk.", TypicalHazardCategories: []string{"communication_failure", "unauthorized_access"}, TypicalEnergySources: []string{}, MapsToComponentType: "network", Tags: []string{"networked", "it_component", "cyber"}, SortOrder: 111},
|
||||
{ID: "C112", NameDE: "Industrie-Router", NameEN: "Industrial Router", Category: "it_network", DescriptionDE: "Router zur Segmentierung und Absicherung des Maschinennetzwerks.", TypicalHazardCategories: []string{"communication_failure", "unauthorized_access"}, TypicalEnergySources: []string{}, MapsToComponentType: "network", Tags: []string{"networked", "it_component", "cyber"}, SortOrder: 112},
|
||||
{ID: "C113", NameDE: "Industrie-Firewall", NameEN: "Industrial Firewall", Category: "it_network", DescriptionDE: "Firewall zum Schutz des OT-Netzwerks vor externen Angriffen.", TypicalHazardCategories: []string{"unauthorized_access"}, TypicalEnergySources: []string{}, MapsToComponentType: "network", Tags: []string{"networked", "it_component", "security_device"}, SortOrder: 113},
|
||||
{ID: "C114", NameDE: "IoT-Gateway", NameEN: "IoT Gateway", Category: "it_network", DescriptionDE: "Gateway fuer die Anbindung von Maschinen an Cloud/Edge.", TypicalHazardCategories: []string{"communication_failure", "unauthorized_access"}, TypicalEnergySources: []string{}, MapsToComponentType: "network", Tags: []string{"networked", "it_component", "has_software"}, SortOrder: 114},
|
||||
{ID: "C115", NameDE: "Edge-Computing-Einheit", NameEN: "Edge Computing Unit", Category: "it_network", DescriptionDE: "Lokale Recheneinheit fuer Datenvorverarbeitung und KI-Inferenz.", TypicalHazardCategories: []string{"software_fault", "communication_failure"}, TypicalEnergySources: []string{}, MapsToComponentType: "network", Tags: []string{"networked", "it_component", "has_software", "has_ai"}, SortOrder: 115},
|
||||
{ID: "C116", NameDE: "WLAN Access Point (Industrie)", NameEN: "Industrial WiFi Access Point", Category: "it_network", DescriptionDE: "Drahtloser Netzwerkzugang im Maschinenumfeld.", TypicalHazardCategories: []string{"communication_failure", "unauthorized_access"}, TypicalEnergySources: []string{}, MapsToComponentType: "network", Tags: []string{"networked", "it_component", "wireless"}, SortOrder: 116},
|
||||
{ID: "C116", NameDE: "WLAN Access Point (Industrie)", NameEN: "Industrial WiFi Access Point", Category: "it_network", DescriptionDE: "Drahtloser Netzwerkzugang im Maschinenumfeld.", TypicalHazardCategories: []string{"communication_failure", "unauthorized_access"}, TypicalEnergySources: []string{}, MapsToComponentType: "network", Tags: []string{"networked", "it_component", "wireless", "cyber"}, SortOrder: 116},
|
||||
{ID: "C117", NameDE: "OPC UA Server", NameEN: "OPC UA Server", Category: "it_network", DescriptionDE: "OPC UA Kommunikationsserver fuer Maschine-zu-Maschine-Vernetzung.", TypicalHazardCategories: []string{"communication_failure", "unauthorized_access"}, TypicalEnergySources: []string{}, MapsToComponentType: "network", Tags: []string{"networked", "it_component", "has_software"}, SortOrder: 117},
|
||||
{ID: "C118", NameDE: "VPN-Appliance", NameEN: "VPN Appliance", Category: "it_network", DescriptionDE: "VPN-Geraet fuer sichere Fernzugriffe auf die Maschinensteuerung.", TypicalHazardCategories: []string{"unauthorized_access"}, TypicalEnergySources: []string{}, MapsToComponentType: "network", Tags: []string{"networked", "it_component", "security_device"}, SortOrder: 118},
|
||||
{ID: "C119", NameDE: "KI-Inferenzmodul", NameEN: "AI Inference Module", Category: "it_network", DescriptionDE: "Dediziertes KI-Modul (GPU/TPU) fuer Echtzeit-Inferenz.", TypicalHazardCategories: []string{"false_classification", "model_drift", "unintended_bias"}, TypicalEnergySources: []string{}, MapsToComponentType: "network", Tags: []string{"has_ai", "has_software", "networked"}, SortOrder: 119},
|
||||
{ID: "C119", NameDE: "KI-Inferenzmodul", NameEN: "AI Inference Module", Category: "it_network", DescriptionDE: "Dediziertes KI-Modul (GPU/TPU) fuer Echtzeit-Inferenz.", TypicalHazardCategories: []string{"false_classification", "model_drift", "unintended_bias"}, TypicalEnergySources: []string{}, MapsToComponentType: "network", Tags: []string{"has_ai", "ai_model", "has_software", "networked", "cyber"}, SortOrder: 119},
|
||||
{ID: "C120", NameDE: "Feldbus-Koppler", NameEN: "Fieldbus Coupler", Category: "it_network", DescriptionDE: "Koppler fuer PROFINET, EtherCAT oder andere Feldbussysteme.", TypicalHazardCategories: []string{"communication_failure"}, TypicalEnergySources: []string{}, MapsToComponentType: "network", Tags: []string{"networked", "it_component"}, SortOrder: 120},
|
||||
|
||||
// ── Extended: Press/Forming Machine Components (C121-C135) ───────────
|
||||
@@ -180,7 +180,7 @@ func GetComponentLibrary() []ComponentLibraryEntry {
|
||||
{ID: "C125", NameDE: "Ruettelplatte / Vibrationsfoerderer", NameEN: "Vibrating Plate / Feeder", Category: "mechanical", DescriptionDE: "Vibrationseinheit zum Sortieren, Ausrichten oder Foerdern von Teilen.", TypicalHazardCategories: []string{"noise_vibration", "ergonomic"}, TypicalEnergySources: []string{"EN01"}, MapsToComponentType: "mechanical", Tags: []string{"vibration_source", "noise_source", "moving_part"}, SortOrder: 125},
|
||||
{ID: "C126", NameDE: "Stempel-Formen-System", NameEN: "Die/Punch Tooling System", Category: "mechanical", DescriptionDE: "Werkzeugset aus Stempel und Matrize fuer Umform- oder Stanzvorgaenge.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN01"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "high_force", "crush_point", "cutting_part"}, SortOrder: 126},
|
||||
{ID: "C127", NameDE: "Transfersystem (Stangen/Greifer)", NameEN: "Transfer System (Bar/Gripper)", Category: "mechanical", DescriptionDE: "Mechanisches Transportsystem zwischen Bearbeitungsstationen.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN01"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "shear_risk", "pinch_point"}, SortOrder: 127},
|
||||
{ID: "C128", NameDE: "Aufzugsportal / Hubwerk", NameEN: "Elevator Portal / Hoist", Category: "mechanical", DescriptionDE: "Hebevorrichtung fuer Materialzufuhr (Kisten, Paletten).", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN01", "EN04"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "gravity_risk", "high_force", "person_under_load"}, SortOrder: 128},
|
||||
{ID: "C128", NameDE: "Aufzugsportal / Hubwerk", NameEN: "Elevator Portal / Hoist", Category: "mechanical", DescriptionDE: "Hebevorrichtung fuer Materialzufuhr (Kisten, Paletten).", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN01", "EN03", "EN04"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "gravity_risk", "high_force", "person_under_load", "crush_point"}, SortOrder: 128},
|
||||
{ID: "C129", NameDE: "Fallrohr / Auswurfschacht", NameEN: "Chute / Ejection Channel", Category: "structural", DescriptionDE: "Schwerkraft-basierter Auswurf fuer fertige oder aussortierte Teile.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN04"}, MapsToComponentType: "mechanical", Tags: []string{"gravity_risk"}, SortOrder: 129},
|
||||
{ID: "C130", NameDE: "Oelfangschale / Auffangwanne", NameEN: "Oil Drip Tray", Category: "structural", DescriptionDE: "Auffangvorrichtung fuer Hydraulikoel, Schmiermittel, Kuehlmittel.", TypicalHazardCategories: []string{"material_environmental"}, TypicalEnergySources: []string{}, MapsToComponentType: "mechanical", Tags: []string{"chemical_risk"}, SortOrder: 130},
|
||||
{ID: "C131", NameDE: "Druckbegrenzungsventil", NameEN: "Pressure Relief Valve", Category: "hydraulic", DescriptionDE: "Sicherheitsventil zur Druckbegrenzung im Hydraulikkreis.", TypicalHazardCategories: []string{"pneumatic_hydraulic"}, TypicalEnergySources: []string{"EN07"}, MapsToComponentType: "actuator", Tags: []string{"hydraulic_part", "safety_device", "high_pressure"}, SortOrder: 131},
|
||||
|
||||
@@ -81,6 +81,10 @@ func (e *DocumentExporter) ExportPDF(
|
||||
e.pdfClassifications(pdf, classifications)
|
||||
}
|
||||
|
||||
// --- Quellen & Lizenzen (Stufe 4 Attribution-Renderer, Task #29) ---
|
||||
pdf.AddPage()
|
||||
e.pdfSourcesAppendix(pdf, hazards, mitigations)
|
||||
|
||||
// --- Footer on every page ---
|
||||
pdf.SetFooterFunc(func() {
|
||||
pdf.SetY(-15)
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
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
|
||||
}
|
||||
@@ -83,6 +83,12 @@ type HazardPattern struct {
|
||||
// feeds into the PLr (required Performance Level) computation,
|
||||
// see ComputePLr.
|
||||
DefaultAvoidability int `json:"default_avoidability,omitempty"` // 1 or 2
|
||||
// SecondaryHarms describes consequential damage chains beyond the
|
||||
// classical IACE Hazard→Harm step: end-customer safety, product
|
||||
// liability, food safety, environmental, reputation, financial.
|
||||
// See secondary_harms.go and the strategy discussion (2026-05-20).
|
||||
// Empty for hazards with no downstream chain.
|
||||
SecondaryHarms []SecondaryHarm `json:"secondary_harms,omitempty"`
|
||||
}
|
||||
|
||||
// ComputePLr returns the required Performance Level (PLr) per EN ISO
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package iace
|
||||
|
||||
// Body-part-specific crush hazards at lift / hoist / scissor-lift endstops.
|
||||
// Bridges the gap that the Kistenhubgeraet re-init exposed: the abstract
|
||||
// "Bremse versagt bei Absenkbewegung" pattern fires, but the concrete
|
||||
// "Fuss unter absenkender Hubplattform" body-part variant did not exist.
|
||||
//
|
||||
// Each pattern restricts to lift-family machine types via MachineTypes,
|
||||
// so a press / CNC / textile project does not pick them up. Mitigations
|
||||
// reference the new M600-M604 (lift endstop) library plus the existing
|
||||
// M001 (geometry), M002 (safety distance), M141 (warning sign).
|
||||
|
||||
func GetLiftEndstopPatterns() []HazardPattern {
|
||||
liftTypes := []string{"lift", "hoist", "elevator", "scissor_lift"}
|
||||
return []HazardPattern{
|
||||
{
|
||||
ID: "HP2100",
|
||||
NameDE: "Fuss-Quetschung unter absenkender Hubplattform am Bodenanschlag",
|
||||
NameEN: "Foot crush under descending lift platform at floor stop",
|
||||
RequiredComponentTags: []string{"crush_point", "gravity_risk", "person_under_load"},
|
||||
RequiredEnergyTags: []string{"gravitational"},
|
||||
MachineTypes: liftTypes,
|
||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||
SuggestedMeasureIDs: []string{"M600", "M601", "M604", "M141"},
|
||||
Priority: 92,
|
||||
ScenarioDE: "Fuss oder Bein des Bedieners gelangt waehrend des Absenkvorgangs unter die " +
|
||||
"Hubplattform. Bei Erreichen der unteren Endlage wird der Fuss zwischen Plattform " +
|
||||
"und Boden gequetscht.",
|
||||
TriggerDE: "Unsachgemaesse Position des Bedieners beim Be-/Entladen, fehlende Schaltleiste, fehlender Trittschutz",
|
||||
HarmDE: "Fussquetschung, Mittelfussfraktur, Zehenamputation",
|
||||
AffectedDE: "Bediener, Wartungspersonal",
|
||||
ZoneDE: "Bodenbereich unter Hubplattform, umlaufende Spalte",
|
||||
DefaultSeverity: 4,
|
||||
DefaultExposure: 3,
|
||||
DefaultAvoidability: 2,
|
||||
ISO12100Section: "6.3.5.5 Quetschen — Mindestabstaende",
|
||||
ClarificationQuestionsDE: []string{
|
||||
"Ist eine umlaufende Quetsch-Schaltleiste an der Plattformunterkante verbaut?",
|
||||
"Ist die Hubgeschwindigkeit am unteren Endanschlag auf <=15 mm/s reduziert (siehe M600)?",
|
||||
"Verhindert ein Trittblech / Unterfahrschutz das Hineinfahren von Fuessen?",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "HP2101",
|
||||
NameDE: "Hand- oder Koerper-Quetschung gegen feste Struktur beim Hochfahren der Hubeinheit",
|
||||
NameEN: "Hand or body crush against fixed structure during lift upward travel",
|
||||
RequiredComponentTags: []string{"crush_point", "gravity_risk"},
|
||||
RequiredEnergyTags: []string{"gravitational"},
|
||||
MachineTypes: liftTypes,
|
||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||
SuggestedMeasureIDs: []string{"M602", "M603", "M600", "M141"},
|
||||
Priority: 90,
|
||||
ScenarioDE: "Beim Hochfahren der Last gelangen Hand oder Koerperteile des Bedieners " +
|
||||
"zwischen die hoechste Position der Hubeinheit (z.B. mit beladener Palette) und " +
|
||||
"eine feste Struktur oberhalb (Decke, Vorbau, Querbalken einer umschliessenden Anlage).",
|
||||
TriggerDE: "Eingriff in den Verfahrweg waehrend Hubvorgang, fehlende konstruktive Begrenzung der Endlage",
|
||||
HarmDE: "Hand- oder Armquetschung, im Extremfall Brustkorbkompression",
|
||||
AffectedDE: "Bediener, Einrichter, Wartungspersonal",
|
||||
ZoneDE: "Oberhalb hoechster Hubposition, Vorbau/Decke der umschliessenden Anlage",
|
||||
DefaultSeverity: 4,
|
||||
DefaultExposure: 2,
|
||||
DefaultAvoidability: 2,
|
||||
ISO12100Section: "6.3.5.5 Quetschen — Mindestabstaende",
|
||||
ClarificationQuestionsDE: []string{
|
||||
"Welcher Mindestabstand zu festen Strukturen oberhalb der hoechsten Hubposition ist gegeben? (Empfehlung: 120 mm fuer Kopf, 100 mm fuer Hand)",
|
||||
"Ist der Tippbetrieb (Hold-to-run) durch ein Testprotokoll mit Stop-Zeit-Messung verifiziert?",
|
||||
"Existiert eine redundante Hardware-Endlage zusaetzlich zur Software-Begrenzung?",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "HP2102",
|
||||
NameDE: "Quetschung Bein/Koerper zwischen Hubeinheit und seitlicher Struktur",
|
||||
NameEN: "Leg/body crush between lift unit and lateral structure",
|
||||
RequiredComponentTags: []string{"crush_point", "gravity_risk", "moving_part"},
|
||||
RequiredEnergyTags: []string{"gravitational"},
|
||||
MachineTypes: liftTypes,
|
||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||
SuggestedMeasureIDs: []string{"M602", "M601", "M141"},
|
||||
Priority: 85,
|
||||
ScenarioDE: "Person befindet sich seitlich neben der Hubeinheit und wird waehrend " +
|
||||
"der Bewegung gegen eine feste Struktur (Regalwand, Stuetze, andere Anlage) gequetscht.",
|
||||
TriggerDE: "Aufenthalt in Quetschzone bei Bewegung, fehlende Absperrung",
|
||||
HarmDE: "Beinfraktur, Beckenquetschung",
|
||||
AffectedDE: "Bediener, vorbeigehende Personen",
|
||||
ZoneDE: "Seitlicher Bereich neben Hubeinheit, Lichte Weite zu festen Strukturen",
|
||||
DefaultSeverity: 4,
|
||||
DefaultExposure: 2,
|
||||
DefaultAvoidability: 2,
|
||||
ISO12100Section: "6.3.5.5 Quetschen — Mindestabstaende",
|
||||
ClarificationQuestionsDE: []string{
|
||||
"Welcher Sicherheitsabstand zu seitlichen festen Strukturen ist gegeben (Empfehlung 500 mm Koerperdurchgang)?",
|
||||
"Ist der Bereich seitlich der Hubeinheit als Gefahrenzone markiert oder abgeschrankt?",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package iace
|
||||
|
||||
// Demonstration patterns showing how the SecondaryHarms field carries
|
||||
// downstream-consequence information through the IACE engine.
|
||||
//
|
||||
// Two real-world scenarios are encoded:
|
||||
//
|
||||
// HP2000 — Glass-shard injection in carbonated-beverage bottling
|
||||
// (the "Cola splitter" example from the IACE strategy
|
||||
// discussion). Primary harm is the operator hit by flying
|
||||
// shards; the secondary chain is product-liability towards
|
||||
// supermarket end-customers.
|
||||
//
|
||||
// HP2001 — Cross-contamination in pharma fill-finish lines.
|
||||
// Primary harm is operator exposure; secondary chain is
|
||||
// patient harm + recall under §74a AMG.
|
||||
//
|
||||
// These two patterns are sufficient as a contract test for the
|
||||
// SecondaryHarms field. Library coverage of more scenarios is a
|
||||
// follow-up task once the persistence layer (DB migration) lands.
|
||||
|
||||
func GetSecondaryHarmDemoPatterns() []HazardPattern {
|
||||
return []HazardPattern{
|
||||
{
|
||||
ID: "HP2000",
|
||||
NameDE: "Glasbruch in Karbonisierungs-Abfueller (Hochdruck)",
|
||||
NameEN: "Glass shatter in carbonated bottling line",
|
||||
RequiredComponentTags: []string{"crush_point", "high_pressure"},
|
||||
RequiredEnergyTags: []string{"pneumatic_pressure"},
|
||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||
Priority: 90,
|
||||
MachineTypes: []string{"bottling", "food_processing", "packaging"},
|
||||
ScenarioDE: "Glasflasche platzt unter CO2-Druck waehrend der Abfuellung. " +
|
||||
"Splitter erreichen den Bediener und koennen ferner in nachfolgende " +
|
||||
"Flaschen eingetragen werden.",
|
||||
TriggerDE: "Materialfehler, ueberhoehter Innendruck, Foerderstoss",
|
||||
HarmDE: "Schnittverletzung Auge/Hand des Bedieners",
|
||||
AffectedDE: "Abfueller, Mitarbeiter Linie",
|
||||
ZoneDE: "Karussell, Schutzkapsel, Foerderband-Auslauf",
|
||||
DefaultSeverity: 4,
|
||||
DefaultExposure: 3,
|
||||
ISO12100Section: "6.4.5.5 Schleudernde Teile",
|
||||
SecondaryHarms: []SecondaryHarm{
|
||||
{
|
||||
Type: SecondaryHarmConsumerSafety,
|
||||
Description: "Restsplitter in der Folgeflasche erreichen ueber den Handel " +
|
||||
"den Endkunden. Verletzungsrisiko Mund/Speiseroehre.",
|
||||
LegalBasis: "ProdHaftG §1, VO (EU) Nr. 178/2002 Art. 14",
|
||||
SuggestedMitigations: []string{
|
||||
"Spueltunnel nach Abfuellung",
|
||||
"Inline-Kamera mit Glasbrucherkennung",
|
||||
"Sperrzone fuer 2 Folgeflaschen bei Bruchereignis",
|
||||
"Glasbruchsensor an Karussell mit Linie-Stopp",
|
||||
},
|
||||
Owner: "product_safety",
|
||||
},
|
||||
{
|
||||
Type: SecondaryHarmFoodSafety,
|
||||
Description: "Rueckruf- und Meldepflicht bei Inverkehrbringen unsicherer " +
|
||||
"Lebensmittel; Rueckverfolgbarkeit Chargen-genau erforderlich.",
|
||||
LegalBasis: "VO (EU) 178/2002 Art. 18, 19; LFGB §40",
|
||||
SuggestedMitigations: []string{
|
||||
"Chargen-Tracking bis Endhaendler",
|
||||
"Schnellwarnsystem RASFF aktiviert halten",
|
||||
"Rueckruf-SOP getestet",
|
||||
},
|
||||
Owner: "qm",
|
||||
},
|
||||
{
|
||||
Type: SecondaryHarmReputation,
|
||||
Description: "Pressemitteilung und Aktienkurs-Reaktion bei Verbraucher-" +
|
||||
"verletzungen / behoerdlichem Rueckruf.",
|
||||
LegalBasis: "ISO 31000 Unternehmensrisiko",
|
||||
SuggestedMitigations: []string{
|
||||
"Krisenkommunikations-Plan",
|
||||
"PR-Bereitschaft 24/7",
|
||||
},
|
||||
Owner: "enterprise_risk",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "HP2001",
|
||||
NameDE: "Kreuzkontamination Pharma Fill-Finish",
|
||||
NameEN: "Cross-contamination pharma fill-finish",
|
||||
RequiredComponentTags: []string{"chemical_risk"},
|
||||
RequiredEnergyTags: []string{"pneumatic_pressure"},
|
||||
GeneratedHazardCats: []string{"chemical_hazard"},
|
||||
Priority: 92,
|
||||
MachineTypes: []string{"pharmaceutical", "food_processing"},
|
||||
ScenarioDE: "Wirkstoff-Rueckstand aus Vorcharge im Linienzwischenraum kontaminiert " +
|
||||
"die Folgecharge.",
|
||||
TriggerDE: "Mangelhaftes CIP, Spuelvolumen unterhalb Validierung",
|
||||
HarmDE: "Bedienerexposition bei Probennahme",
|
||||
AffectedDE: "Anlagenbediener, Probenehmer",
|
||||
ZoneDE: "Abfuelllinie zwischen Vorlage und Filler",
|
||||
DefaultSeverity: 4,
|
||||
DefaultExposure: 2,
|
||||
ISO12100Section: "6.4.4 Chemische und biologische Gefaehrdungen",
|
||||
SecondaryHarms: []SecondaryHarm{
|
||||
{
|
||||
Type: SecondaryHarmConsumerSafety,
|
||||
Description: "Patient erhaelt Arzneimittel mit unzulaessiger Beimischung; " +
|
||||
"Wirkungsbeeintraechtigung oder unerwuenschte Wirkung moeglich.",
|
||||
LegalBasis: "AMG §5 (Verkehrsfaehigkeit), §74a (Stufenplan)",
|
||||
SuggestedMitigations: []string{
|
||||
"CIP-Validierung mit TOC- und Conductivity-Limits",
|
||||
"Dedizierte Linien fuer Hochpotente Wirkstoffe",
|
||||
"Stufenplan-Meldung bei Verdacht",
|
||||
},
|
||||
Owner: "qm",
|
||||
},
|
||||
{
|
||||
Type: SecondaryHarmProductLiability,
|
||||
Description: "Haftung des Inverkehrbringers nach AMG §84 (Gefaehrdungshaftung " +
|
||||
"bei Arzneimittelschaeden, verschuldensunabhaengig).",
|
||||
LegalBasis: "AMG §84",
|
||||
SuggestedMitigations: []string{
|
||||
"Deckung Produkthaftpflicht ueber gesetzliches Minimum",
|
||||
"Chargen-Rueckhaltemuster 12 Monate ueber MHD hinaus",
|
||||
},
|
||||
Owner: "legal",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -29,8 +29,18 @@ func GetKeywordDictionary() []KeywordEntry {
|
||||
// ── Foerdertechnik ──────────────────────────────────────────────
|
||||
{Keywords: []string{"foerderband", "transportband", "conveyor"}, ComponentIDs: []string{"C003"}, EnergyIDs: []string{"EN01", "EN02"}, ExtraTags: []string{"entanglement_risk"}},
|
||||
{Keywords: []string{"transfer", "transferanlage", "transfersystem"}, ComponentIDs: []string{"C127"}, ExtraTags: []string{"shear_risk", "pinch_point"}},
|
||||
{Keywords: []string{"aufzug", "elevator", "lift"}, ComponentIDs: []string{"C014", "C128"}, EnergyIDs: []string{"EN04"}, ExtraTags: []string{"gravity_risk", "person_under_load"}},
|
||||
{Keywords: []string{"hubwerk", "hoist", "hubgeraet"}, ComponentIDs: []string{"C128"}, EnergyIDs: []string{"EN04"}, ExtraTags: []string{"gravity_risk", "person_under_load"}},
|
||||
// Hubgeraete: korrigiert auf EN03 (Potentielle/Gravitational) statt
|
||||
// nur EN04 (Elektrisch). Audit-Methode A zeigte, dass HP1014/HP1015/
|
||||
// HP1017/HP1018 (alle Quetsch-Patterns unter absenkender Last) nicht
|
||||
// zuendeten weil sowohl crush_point als auch gravitational fehlten.
|
||||
// EN04 bleibt fuer Steuerstrom-bezogene Patterns mit drin.
|
||||
{Keywords: []string{"aufzug", "elevator", "lift"}, ComponentIDs: []string{"C014", "C128"}, EnergyIDs: []string{"EN03", "EN04"}, ExtraTags: []string{"gravity_risk", "person_under_load", "crush_point"}},
|
||||
{Keywords: []string{"hubwerk", "hoist", "hubgeraet"}, ComponentIDs: []string{"C128"}, EnergyIDs: []string{"EN03", "EN04"}, ExtraTags: []string{"gravity_risk", "person_under_load", "crush_point"}},
|
||||
// Hub-Verben aus Methode-C-Vocabulary-Diff: "absenken/senken/
|
||||
// anheben/heben/hubhoehe" tauchten im Limits-Form auf, der Parser
|
||||
// kannte sie nicht. Konservativ EN03 + Tags, Component bleibt offen.
|
||||
{Keywords: []string{"absenk", "senken", "anheben", "heben"}, EnergyIDs: []string{"EN03"}, ExtraTags: []string{"gravity_risk", "person_under_load", "crush_point"}},
|
||||
{Keywords: []string{"hubhoehe", "hubweg", "hubgeschwindig"}, EnergyIDs: []string{"EN03"}, ExtraTags: []string{"gravity_risk", "crush_point"}},
|
||||
{Keywords: []string{"ruettel", "vibration", "vibrationsfoerderer"}, ComponentIDs: []string{"C125"}, ExtraTags: []string{"vibration_source", "noise_source"}},
|
||||
{Keywords: []string{"fallrohr", "auswurf", "chute"}, ComponentIDs: []string{"C129"}, EnergyIDs: []string{"EN04"}, ExtraTags: []string{"gravity_risk"}},
|
||||
{Keywords: []string{"kistenwechsel", "bin change"}, ComponentIDs: []string{"C134"}, ExtraTags: []string{"ergonomic", "gravity_risk"}},
|
||||
|
||||
@@ -21,6 +21,7 @@ func GetProtectiveMeasureLibrary() []ProtectiveMeasureEntry {
|
||||
all = append(all, GetTextileAgriMeasures()...) // Textil + Landmaschinen (Phase 5)
|
||||
all = append(all, getGTBremseMeasures()...) // GT-Bremse-Coverage-Gaps (M483-M522)
|
||||
all = append(all, GetCRAMeasures()...) // CRA / DIN EN 40000-1-2 cyber-resilience (M540-M548)
|
||||
all = append(all, getLiftEndstopMeasures()...) // Lift/hoist endstop (M600-M604) — bridges OSHA MD library
|
||||
return all
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
package iace
|
||||
|
||||
// Lift / hoist / scissor-lift endstop mitigations — bridges the OSHA
|
||||
// minimum-distance library (minimum_distances.go, Task #18) into the
|
||||
// pattern-engine measure library. Each entry cites the concrete OSHA
|
||||
// value AND its EU-norm pendant by identifier only.
|
||||
//
|
||||
// Engineering rounding values come from MD_OSHA_* IDs in
|
||||
// minimum_distances.go. We do not duplicate the source text here —
|
||||
// the Tech-File renderer can join MD_OSHA_* into the rendered text
|
||||
// at output time.
|
||||
|
||||
func getLiftEndstopMeasures() []ProtectiveMeasureEntry {
|
||||
return []ProtectiveMeasureEntry{
|
||||
// M600 — Cruise/creep speed at end of travel
|
||||
{
|
||||
ID: "M600",
|
||||
ReductionType: "protection",
|
||||
SubType: "speed_control",
|
||||
Name: "Kriechgeschwindigkeit am Endanschlag (Hubgeraete)",
|
||||
Description: "Hubgeschwindigkeit am Ende der Verfahrbewegung (oben und unten) auf maximal 15 mm/s " +
|
||||
"reduzieren. OSHA 29 CFR 1910.217 Hand-Speed-Konstante 63 in/s = 1.600 mm/s als Obergrenze " +
|
||||
"fuer Stopp-Reaktionszeit. Damit ist auch bei spaeter Auslosung der Quetsch-Schaltleiste " +
|
||||
"genug Bremsweg vorhanden.",
|
||||
HazardCategory: "mechanical",
|
||||
Examples: []string{
|
||||
"Hub-Endschalter mit Soft-Stop und Geschwindigkeitsstufe < 15 mm/s in den letzten 50 mm",
|
||||
"Servo-Antrieb mit Ramp-down-Profil ueber die letzten 100 mm Verfahrweg",
|
||||
"Drehzahl-Begrenzer im Frequenzumrichter mit Endlagen-Trigger",
|
||||
},
|
||||
NormReferences: []string{
|
||||
"OSHA 29 CFR 1910.217 (Ds = 63 in/s x Ts)",
|
||||
"EN ISO 13855 (Anordnung von Schutzeinrichtungen)",
|
||||
"EN 1570-1 (Hubtische — Bauanforderungen)",
|
||||
},
|
||||
RiskReduction: &RiskReduction{SeverityDelta: -1, ExposureDelta: -1, ProbabilityDelta: -1},
|
||||
Tags: []string{"crush_point", "gravity_risk", "speed_limit"},
|
||||
},
|
||||
// M601 — Trip-edge sensor under platform (safety bumper)
|
||||
{
|
||||
ID: "M601",
|
||||
ReductionType: "protection",
|
||||
SubType: "safety_device",
|
||||
Name: "Quetsch-Schaltleiste unterhalb der Hubplattform",
|
||||
Description: "Druckempfindliche Schaltleiste (gemaess EN ISO 13856-2) am unteren Rand der Hubplattform " +
|
||||
"loest bei Beruehrung den Hubantrieb sofort aus und kehrt die Bewegung um. Verhindert Quetschung " +
|
||||
"von Fuessen oder Beinen unter absenkender Last. PL c oder hoeher nach EN ISO 13849-1.",
|
||||
HazardCategory: "mechanical",
|
||||
Examples: []string{
|
||||
"Schaltleiste umlaufend an Bodenkante der Hubplattform",
|
||||
"Trittschutz mit redundanter Auswertung am Hubtisch",
|
||||
"Lichtgitter im Bodenbereich als Ergaenzung bei freistehenden Anlagen",
|
||||
},
|
||||
NormReferences: []string{
|
||||
"EN ISO 13856-2 (Schaltleisten)",
|
||||
"EN ISO 13849-1 (PL-Bestimmung)",
|
||||
"EN 1570-1",
|
||||
},
|
||||
RiskReduction: &RiskReduction{SeverityDelta: -2, ExposureDelta: -2, ProbabilityDelta: -2},
|
||||
Tags: []string{"crush_point", "gravity_risk", "safety_device"},
|
||||
},
|
||||
// M602 — Minimum clearance to fixed structure above max lift position
|
||||
{
|
||||
ID: "M602",
|
||||
ReductionType: "design",
|
||||
SubType: "geometry",
|
||||
Name: "Mindestabstand zu festen Strukturen oberhalb der Hubendlage",
|
||||
Description: "Zwischen hoechstem Punkt der Hubeinheit (mit beladenem Werkstueck) und festen Strukturen " +
|
||||
"oberhalb (Decke, Vorbau, Querbalken) muss ein Sicherheitsabstand verbleiben, der das Quetschen " +
|
||||
"von Haenden und Koerper verhindert. Empfehlung: 120 mm fuer Kopf, 100 mm fuer Hand, 25 mm fuer " +
|
||||
"Finger — abgeleitet aus EN 349 / EN ISO 13854 unabhaengig zu pruefen.",
|
||||
HazardCategory: "mechanical",
|
||||
Examples: []string{
|
||||
"Konstruktive Begrenzung der oberen Hubposition durch mechanischen Anschlag",
|
||||
"Software-Endlage mit redundantem Hardware-Sicherheitsschalter",
|
||||
"Auslegungs-Pruefung mit beladener Standard-Palette und Maximal-Hubhoehe",
|
||||
},
|
||||
NormReferences: []string{
|
||||
"EN 349 (Mindestabstaende gegen Quetschen von Koerperteilen)",
|
||||
"EN ISO 13854 (Mindestabstaende gegen Quetschen)",
|
||||
"OSHA 29 CFR 1910.212(a)(5) (Lueftergitter ≤ 1/2 in als Anker)",
|
||||
},
|
||||
RiskReduction: &RiskReduction{SeverityDelta: -2, ExposureDelta: -1},
|
||||
Tags: []string{"crush_point", "gravity_risk"},
|
||||
},
|
||||
// M603 — Hold-to-run with two-hand operation for manual descent
|
||||
{
|
||||
ID: "M603",
|
||||
ReductionType: "protection",
|
||||
SubType: "control_device",
|
||||
Name: "Tippbetrieb / Hold-to-run beim Absenken (mit Verifikations-Nachweis)",
|
||||
Description: "Absenken nur im Tippbetrieb (Hold-to-run): Bedientaster muss waehrend des gesamten " +
|
||||
"Absenkvorgangs gedrueckt gehalten werden. Bei Loslassen stoppt die Bewegung sofort. " +
|
||||
"Im Limits-Form als 'Tippbetrieb' deklariert — durch Tests verifizieren (Stop-Reaktionszeit " +
|
||||
"<= 0,3 s im voll beladenen Zustand).",
|
||||
HazardCategory: "mechanical",
|
||||
Examples: []string{
|
||||
"Tipptaster mit elektrischer Selbstrueckstellung",
|
||||
"Zweihand-Bedienung fuer kritische Absenk-Bereiche (Tipp + Zustimmtaster)",
|
||||
"Pruefprotokoll Stop-Zeit gemaess EN ISO 13849-1 PL c",
|
||||
},
|
||||
NormReferences: []string{
|
||||
"EN ISO 13849-1 (Sicherheitsbezogene Steuerungsteile)",
|
||||
"EN ISO 13851 (Zweihandschaltungen)",
|
||||
"BetrSichV § 4 (Schutzmassnahmen)",
|
||||
},
|
||||
RiskReduction: &RiskReduction{SeverityDelta: -1, ExposureDelta: -2, ProbabilityDelta: -1},
|
||||
Tags: []string{"crush_point", "gravity_risk", "control_device"},
|
||||
},
|
||||
// M604 — Underrun guard / kick plate at platform base
|
||||
{
|
||||
ID: "M604",
|
||||
ReductionType: "design",
|
||||
SubType: "geometry",
|
||||
Name: "Trittblech / Unterfahrschutz an der Hubplattform",
|
||||
Description: "Unter der Hubplattform befindet sich ein umlaufendes Trittblech oder Unterfahrschutz, " +
|
||||
"das das Hineinfahren von Fuessen unter die Plattform mechanisch verhindert. Hoehe ueber Boden " +
|
||||
"maximal 5 mm in unterster Stellung. Trittblech haelt die Last eines Schuhs (mind. 150 kg) " +
|
||||
"ohne Verformung.",
|
||||
HazardCategory: "mechanical",
|
||||
Examples: []string{
|
||||
"Umlaufendes Stahlblech 3 mm Wandstaerke mit Fasen-Kante",
|
||||
"Kombination mit M601 (Schaltleiste) als doppelte Sicherung",
|
||||
"Pruefung jaehrlich auf Verformung und Funktion der Auflage",
|
||||
},
|
||||
NormReferences: []string{
|
||||
"EN 1570-1 (Hubtische)",
|
||||
"EN ISO 13857 (Sicherheitsabstaende)",
|
||||
},
|
||||
RiskReduction: &RiskReduction{SeverityDelta: -2, ExposureDelta: -1},
|
||||
Tags: []string{"crush_point", "gravity_risk"},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package iace
|
||||
|
||||
// Minimum-distance library — Task #18.
|
||||
//
|
||||
// Anchor source: OSHA 29 CFR 1910 Subpart O (US Federal Public Domain,
|
||||
// 17 U.S.C. §105). The values below are reproduced verbatim from the
|
||||
// Federal Code; conversions to metric are mathematical and carry no
|
||||
// copyright. Engineering rounding to safe-side mm values is BreakPilot's
|
||||
// recommendation and labelled as such.
|
||||
//
|
||||
// EU norm equivalents (EN ISO 13857, EN 349, EN 13855, EN 1010) are
|
||||
// referenced by identifier only — no values are reproduced, because
|
||||
// DIN/Beuth retain copyright on the wording. The DINComparisonNote
|
||||
// field carries a human-curated judgement on whether the EU norm is
|
||||
// stricter / looser / equivalent — this is a qualitative observation
|
||||
// about a publicly available document, not a copy of its text.
|
||||
//
|
||||
// See LICENSE_RULES.md and project_attribution_strategy.md for the
|
||||
// licensing logic. The OSHA values are R1 (verbatim public domain);
|
||||
// the recommended metric values are BreakPilot engineering output (R3
|
||||
// own-work). DIN references are R3 identifier-only.
|
||||
|
||||
// MinimumDistanceUnit denotes the original unit system of the source.
|
||||
type MinimumDistanceUnit string
|
||||
|
||||
const (
|
||||
UnitInch MinimumDistanceUnit = "inch"
|
||||
UnitFoot MinimumDistanceUnit = "foot"
|
||||
UnitMeter MinimumDistanceUnit = "meter"
|
||||
UnitMM MinimumDistanceUnit = "mm"
|
||||
)
|
||||
|
||||
// MinimumDistance is the data contract for a single safety-distance rule.
|
||||
// It can be (a) a fixed gap value, (b) a distance range, or (c) a formula
|
||||
// like OSHA's Ds = 63 in/s × Ts (hand-speed constant).
|
||||
type MinimumDistance struct {
|
||||
ID string `json:"id"` // MD_OSHA_001
|
||||
// Source identifier — full CFR citation or norm reference.
|
||||
SourceCFR string `json:"source_cfr,omitempty"` // "29 CFR §1910.217(c)(1)(i)"
|
||||
SourceTable string `json:"source_table,omitempty"` // "Table O-10"
|
||||
License string `json:"license"` // "US Federal Public Domain"
|
||||
LicenseRule int `json:"license_rule"` // 1 / 2 / 3 (see LICENSE_RULES.md)
|
||||
|
||||
// Original verbatim value in the source's own unit.
|
||||
OriginalUnit MinimumDistanceUnit `json:"original_unit"`
|
||||
OriginalValue float64 `json:"original_value,omitempty"`
|
||||
OriginalMin float64 `json:"original_min,omitempty"`
|
||||
OriginalMax float64 `json:"original_max,omitempty"`
|
||||
|
||||
// Exact conversion to mm — no engineering rounding.
|
||||
ExactMM float64 `json:"exact_mm,omitempty"`
|
||||
ExactMinMM float64 `json:"exact_min_mm,omitempty"`
|
||||
ExactMaxMM float64 `json:"exact_max_mm,omitempty"`
|
||||
|
||||
// Engineering-recommended metric value with safe-side rounding.
|
||||
// For minimum distances: rounded up. For maximum opening widths:
|
||||
// rounded down.
|
||||
RecommendedMM int `json:"recommended_mm,omitempty"`
|
||||
RecommendedMinMM int `json:"recommended_min_mm,omitempty"`
|
||||
RecommendedMaxMM int `json:"recommended_max_mm,omitempty"`
|
||||
RoundingNote string `json:"rounding_note,omitempty"`
|
||||
|
||||
// Optional formula constant (e.g. OSHA hand-speed 63 in/s).
|
||||
FormulaInchPerSecond float64 `json:"formula_inch_per_second,omitempty"`
|
||||
FormulaMMPerSecond float64 `json:"formula_mm_per_second,omitempty"`
|
||||
FormulaDescription string `json:"formula_description,omitempty"`
|
||||
|
||||
Context string `json:"context"` // "Point of Operation Guarding mechanical presses"
|
||||
BodyPart string `json:"body_part,omitempty"` // "finger" / "hand" / "head" / "foot" / "body"
|
||||
HazardTags []string `json:"hazard_tags,omitempty"` // [crush_point, cutting_part, ...]
|
||||
|
||||
// EU norm cross-reference — IDENTIFIER ONLY, no values reproduced.
|
||||
EUNormHints []EUNormHint `json:"eu_norm_hints,omitempty"`
|
||||
}
|
||||
|
||||
// EUNormHint references an EU standard by identifier without reproducing
|
||||
// any value or text from it. The DINComparisonNote is a human-curated
|
||||
// qualitative judgement (stricter / equivalent / looser) — not a copy.
|
||||
type EUNormHint struct {
|
||||
Norm string `json:"norm"` // "EN ISO 13857"
|
||||
Section string `json:"section,omitempty"` // "Tab. 4, Schutz gegen Hineingreifen"
|
||||
DINComparisonNote string `json:"din_comparison_note,omitempty"`
|
||||
}
|
||||
|
||||
// GetOSHAMinimumDistances returns the verbatim OSHA values for
|
||||
// machine-guarding distances. All values are US Federal Public Domain
|
||||
// (17 U.S.C. §105). Engineering rounding is BreakPilot's safe-side
|
||||
// recommendation; OSHA values themselves are unchanged.
|
||||
func GetOSHAMinimumDistances() []MinimumDistance {
|
||||
return []MinimumDistance{
|
||||
// OSHA Table O-10 row 1 — verbatim values, mathematical conversion,
|
||||
// safe-side rounded engineering recommendation.
|
||||
{
|
||||
ID: "MD_OSHA_O10_R1",
|
||||
SourceCFR: "29 CFR §1910.217(c)(1)(i)",
|
||||
SourceTable: "Table O-10 row 1",
|
||||
License: "US Federal Public Domain (17 U.S.C. §105)",
|
||||
LicenseRule: 1,
|
||||
OriginalUnit: UnitInch,
|
||||
OriginalMin: 0.5, OriginalMax: 1.5, OriginalValue: 0.25,
|
||||
ExactMinMM: 12.7, ExactMaxMM: 38.1, ExactMM: 6.35,
|
||||
RecommendedMinMM: 15, RecommendedMaxMM: 40, RecommendedMM: 6,
|
||||
RoundingNote: "Distance auf 5-mm-Raster aufgerundet, opening auf 1-mm-Raster abgerundet (konservativ in beide Richtungen).",
|
||||
Context: "Point-of-Operation Guarding bei mechanischen Pressen",
|
||||
BodyPart: "finger",
|
||||
HazardTags: []string{"crush_point", "cutting_part"},
|
||||
EUNormHints: []EUNormHint{
|
||||
{Norm: "EN ISO 13857", Section: "Tab. 4 (Hineingreifen)",
|
||||
DINComparisonNote: "Andere Methodik (Reichweitenmodell). Unabhaengig pruefen — Werte koennen abweichen."},
|
||||
},
|
||||
},
|
||||
// OSHA Table O-10 row 4 — used as a worked example in the strategy
|
||||
// discussion. Distance 3.5-5.5 in, opening max 5/8 in.
|
||||
{
|
||||
ID: "MD_OSHA_O10_R4",
|
||||
SourceCFR: "29 CFR §1910.217(c)(1)(i)",
|
||||
SourceTable: "Table O-10 row 4",
|
||||
License: "US Federal Public Domain (17 U.S.C. §105)",
|
||||
LicenseRule: 1,
|
||||
OriginalUnit: UnitInch,
|
||||
OriginalMin: 3.5, OriginalMax: 5.5, OriginalValue: 0.625,
|
||||
ExactMinMM: 88.9, ExactMaxMM: 139.7, ExactMM: 15.875,
|
||||
RecommendedMinMM: 90, RecommendedMaxMM: 140, RecommendedMM: 15,
|
||||
RoundingNote: "Distance 88.9→90 (+1.1 mm), 139.7→140 (+0.3 mm) aufgerundet; Opening 15.875→15 (-0.875 mm) abgerundet.",
|
||||
Context: "Point-of-Operation Guarding bei mechanischen Pressen",
|
||||
BodyPart: "finger",
|
||||
HazardTags: []string{"crush_point", "cutting_part"},
|
||||
EUNormHints: []EUNormHint{
|
||||
{Norm: "EN ISO 13857", Section: "Tab. 4 (Hineingreifen)",
|
||||
DINComparisonNote: "Andere Methodik (Reichweitenmodell). Compliance-Annotation pflegen."},
|
||||
},
|
||||
},
|
||||
// OSHA §1910.212(a)(5) — fan blade guards. Verbatim 1/2 inch.
|
||||
{
|
||||
ID: "MD_OSHA_212_FAN",
|
||||
SourceCFR: "29 CFR §1910.212(a)(5)",
|
||||
License: "US Federal Public Domain (17 U.S.C. §105)",
|
||||
LicenseRule: 1,
|
||||
OriginalUnit: UnitInch,
|
||||
OriginalValue: 0.5,
|
||||
ExactMM: 12.7,
|
||||
RecommendedMM: 12,
|
||||
RoundingNote: "Luefterblatt-Schutzgitter: max. Spaltoeffnung 1/2 in = 12.7 mm. Konservativ auf 12 mm abgerundet.",
|
||||
Context: "Lüfterblätter unter 7 ft (2.13 m) Höhe",
|
||||
BodyPart: "finger",
|
||||
HazardTags: []string{"rotating_part", "cutting_part"},
|
||||
EUNormHints: []EUNormHint{
|
||||
{Norm: "EN ISO 13857", Section: "Tab. 4",
|
||||
DINComparisonNote: "DIN-Wert pruefen."},
|
||||
},
|
||||
},
|
||||
// OSHA §1910.217 Hand-Speed Constant — formula Ds = 63 in/s × Ts
|
||||
{
|
||||
ID: "MD_OSHA_217_PSDI",
|
||||
SourceCFR: "29 CFR §1910.217 (Ds = 63 in/s × Ts)",
|
||||
License: "US Federal Public Domain (17 U.S.C. §105)",
|
||||
LicenseRule: 1,
|
||||
OriginalUnit: UnitInch,
|
||||
FormulaInchPerSecond: 63.0,
|
||||
FormulaMMPerSecond: 1600.2,
|
||||
FormulaDescription: "Hand-Speed-Konstante 63 in/s ≈ 1600 mm/s. " +
|
||||
"Ds (Mindestabstand) = 63 × Ts (Stoppzeit Presse in Sekunden).",
|
||||
Context: "PSDI Presence-Sensing Device Initiation und Two-Hand-Trip",
|
||||
BodyPart: "hand",
|
||||
HazardTags: []string{"crush_point", "high_speed"},
|
||||
EUNormHints: []EUNormHint{
|
||||
{Norm: "EN 13855", Section: "Sicherheitsabstaende",
|
||||
DINComparisonNote: "EN 13855 nutzt andere Konstante (1600 mm/s ≈ identisch); EU-Norm unabhaengig pruefen."},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package iace
|
||||
|
||||
// Norm cross-reference matrix: maps a core ISO/IEC/EN standard to the
|
||||
// jurisdiction-specific identifiers used in DIN (DE), ANSI / NFPA / UL (US),
|
||||
// GB (China), and JIS (Japan). This is an identifier-only mapping — no
|
||||
// copyrighted norm text is included. The matrix is used to render a
|
||||
// "this requirement also satisfies X in market Y" hint in tech files,
|
||||
// enabling dual-use compliance documents for CE + US/CN/JP export.
|
||||
//
|
||||
// IMPORTANT: each NormMapping carries an explicit Confidence and Relation.
|
||||
// Do NOT treat "partial" or "medium" entries as 1:1 substitutes. They
|
||||
// indicate scope overlap that must be verified by a competent person for
|
||||
// the concrete machine before relying on the foreign standard.
|
||||
|
||||
// NormMapping is one entry in the cross-reference table.
|
||||
type NormMapping struct {
|
||||
Region string `json:"region"` // "EU-DIN", "US-ANSI", "US-NFPA", "US-UL", "US-OSHA", "CN-GB", "JP-JIS", "INTL-ISO"
|
||||
Identifier string `json:"identifier"` // e.g. "DIN EN ISO 12100:2011"
|
||||
Relation string `json:"relation"` // "identical", "equivalent", "partial", "supersedes", "superseded_by"
|
||||
Confidence string `json:"confidence"` // "verified", "high", "medium", "low"
|
||||
Notes string `json:"notes,omitempty"` // Optional scope clarification (e.g. "only chapters 4-6")
|
||||
SourceURL string `json:"source_url,omitempty"` // Optional pointer to a public catalog entry
|
||||
}
|
||||
|
||||
// NormCrossRef is the cross-reference entry for one NormReference.ID.
|
||||
type NormCrossRef struct {
|
||||
NormID string `json:"norm_id"` // Matches NormReference.ID (e.g. "ISO-12100")
|
||||
Mappings []NormMapping `json:"mappings"` // International equivalents
|
||||
Notes string `json:"notes,omitempty"` // General notes about the cross-walk
|
||||
BatchID string `json:"batch_id"` // Tracking which batch added this entry
|
||||
}
|
||||
|
||||
// crossRefRegistry is the in-memory registry, populated by init() in each batch file.
|
||||
var crossRefRegistry = map[string]NormCrossRef{}
|
||||
|
||||
// registerCrossRefs is called by each batch file's init() to append entries.
|
||||
func registerCrossRefs(entries []NormCrossRef) {
|
||||
for _, e := range entries {
|
||||
crossRefRegistry[e.NormID] = e
|
||||
}
|
||||
}
|
||||
|
||||
// GetNormCrossRef returns the cross-reference entry for a given NormReference.ID,
|
||||
// or a zero value with NormID set if no mapping exists yet.
|
||||
func GetNormCrossRef(normID string) NormCrossRef {
|
||||
if entry, ok := crossRefRegistry[normID]; ok {
|
||||
return entry
|
||||
}
|
||||
return NormCrossRef{NormID: normID, Mappings: []NormMapping{}}
|
||||
}
|
||||
|
||||
// ListNormCrossRefs returns every entry in the registry. Used by the
|
||||
// /norms-library/crossref bulk endpoint and for tech-file batch rendering.
|
||||
func ListNormCrossRefs() []NormCrossRef {
|
||||
out := make([]NormCrossRef, 0, len(crossRefRegistry))
|
||||
for _, v := range crossRefRegistry {
|
||||
out = append(out, v)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// CrossRefCoverage returns counters that let the UI render a progress bar
|
||||
// ("X of Y norms have a cross-reference"). The "total" comes from the
|
||||
// caller (norms library size) since the cross-ref package does not depend
|
||||
// on the norms library to avoid a cyclic import.
|
||||
func CrossRefCoverage(totalNorms int) (covered, total int) {
|
||||
return len(crossRefRegistry), totalNorms
|
||||
}
|
||||
@@ -0,0 +1,443 @@
|
||||
package iace
|
||||
|
||||
// Cross-reference matrix — Batch 1a (IDs 1-50 in norms_library.go load order).
|
||||
// Covers A-norms (Grundnormen) and B1-norms (Sicherheitsgrundnormen) +
|
||||
// early B2-norms. These are the most internationally harmonized standards
|
||||
// and therefore have the strongest "verified"/"high" confidence mappings.
|
||||
|
||||
func init() {
|
||||
registerCrossRefs(batch1aCrossRefs())
|
||||
}
|
||||
|
||||
// batch1aCrossRefs contains entries 1-50.
|
||||
func batch1aCrossRefs() []NormCrossRef {
|
||||
return []NormCrossRef{
|
||||
{
|
||||
NormID: "ISO-12100", BatchID: "1a",
|
||||
Notes: "Foundational machinery safety standard, harmonized via ISO/TC 199. Globally aligned.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 12100:2011-03", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.0:2020 (Safety of Machinery)", Relation: "partial", Confidence: "high", Notes: "Scope similar; US framework uses task-based risk assessment in addition."},
|
||||
{Region: "CN-GB", Identifier: "GB/T 15706-2012", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS B 9700:2013", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-13849-1", BatchID: "1a",
|
||||
Notes: "Functional safety of safety-related control parts via Performance Level. Strong international alignment.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 13849-1:2024-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.26-2018 (Functional Safety for Equipment)", Relation: "partial", Confidence: "high", Notes: "US uses both PL (ISO 13849) and SIL (IEC 62061) within B11.26."},
|
||||
{Region: "CN-GB", Identifier: "GB/T 16855.1-2018", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS B 9705-1:2019", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-13849-2", BatchID: "1a",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 13849-2:2013-02", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 16855.2-2015", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS B 9705-2:2019", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "IEC-62061", BatchID: "1a",
|
||||
Notes: "Functional safety via SIL approach. IEC standard, regional adoptions vary.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN IEC 62061:2022-07", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.26-2018", Relation: "partial", Confidence: "high", Notes: "B11.26 combines IEC 62061 + ISO 13849-1."},
|
||||
{Region: "CN-GB", Identifier: "GB 28526-2012", Relation: "equivalent", Confidence: "medium"},
|
||||
{Region: "JP-JIS", Identifier: "JIS B 9961:2008", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-13857", BatchID: "1a",
|
||||
Notes: "Safety distances against reaching upper/lower limbs into hazardous zones.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 13857:2020-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.19-2019 (Performance Criteria for Safeguarding)", Relation: "partial", Confidence: "high", Notes: "Includes safety distance tables with imperial units."},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.212 (Machine Guarding)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 23821-2009", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS B 9718:2013", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-13855", BatchID: "1a",
|
||||
Notes: "Positioning of safeguards relative to approach speed of body parts.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 13855:2010-10", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.19-2019 (Annex on Safety Distance)", Relation: "partial", Confidence: "high", Notes: "US uses Ds = K × (Ts + Tc) formula; imperial."},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.217 Table O-10", Relation: "partial", Confidence: "high", Notes: "OSHA hand-speed constant K = 63 in/s."},
|
||||
{Region: "CN-GB", Identifier: "GB/T 19876-2012", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS B 9715:2013", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-14120", BatchID: "1a",
|
||||
Notes: "Design and construction of fixed and movable guards.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 14120:2016-05", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.19-2019 §6 (Guards)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 8196-2018", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS B 9716:2013", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-14119", BatchID: "1a",
|
||||
Notes: "Interlocking devices associated with guards — design and selection.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 14119:2014-03", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.19-2019 §7 (Interlocks)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 18831-2017", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-60204-1", BatchID: "1a",
|
||||
Notes: "Electrical equipment of machines — general requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60204-1:2019-06 (VDE 0113-1)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 60204-1:2016", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 79:2024 (Electrical Standard for Industrial Machinery)", Relation: "equivalent", Confidence: "high", Notes: "NFPA 79 is the US adaptation; differences in earthing/grounding terminology."},
|
||||
{Region: "US-UL", Identifier: "UL 508A:2018 (Industrial Control Panels)", Relation: "partial", Confidence: "high", Notes: "Panel-shop side; pairs with NFPA 79."},
|
||||
{Region: "CN-GB", Identifier: "GB 5226.1-2019", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS B 9960-1:2019", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-13850", BatchID: "1a",
|
||||
Notes: "Emergency stop function — design principles.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 13850:2016-05", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.19-2019 §11 (Emergency Stop)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 79:2024 §10.7 (Emergency Stop)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 16754-2008", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS B 9703:2019", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "IEC-61496-1", BatchID: "1a",
|
||||
Notes: "Electro-sensitive protective equipment (ESPE) — general requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN IEC 61496-1:2021-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-UL", Identifier: "UL 61496-1:2020", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.19-2019 §8 (Presence-Sensing Devices)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 19436.1-2013", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS B 9704-1:2014", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-4413", BatchID: "1a",
|
||||
Notes: "Hydraulic fluid power — general rules and safety requirements for systems.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4413:2011-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/(NFPA) T2.24.1:2009 (Hydraulic Fluid Power)", Relation: "partial", Confidence: "medium"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 3766-2015", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS B 8361:2012", Relation: "equivalent", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-4414", BatchID: "1a",
|
||||
Notes: "Pneumatic fluid power — general rules and safety requirements for systems.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4414:2011-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 7932-2017", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS B 8370:2011", Relation: "equivalent", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1037", BatchID: "1a",
|
||||
Notes: "Prevention of unexpected start-up. Now superseded by ISO 14118; legacy reference.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1037:1996+A1:2008 (withdrawn 2020, replaced by EN ISO 14118)", Relation: "superseded_by", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 14118:2017", Relation: "supersedes", Confidence: "verified"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.147 (LOTO — Lockout/Tagout)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-11228-1", BatchID: "1a",
|
||||
Notes: "Ergonomics — manual lifting and carrying.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1005-2:2009-04 / DIN EN ISO 11228-1:2022", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ASSP Z365 (Manual Material Handling, draft)", Relation: "partial", Confidence: "medium"},
|
||||
{Region: "US-OSHA", Identifier: "NIOSH Lifting Equation (RWL, Revised 1991)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS Z 8504:2010", Relation: "equivalent", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-11204", BatchID: "1a",
|
||||
Notes: "Acoustics — noise emitted by machinery and equipment, work-station measurement.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11204:2010-10", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI S12.43-1997 (R2007)", Relation: "partial", Confidence: "medium"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 17248.2-1998", Relation: "equivalent", Confidence: "medium"},
|
||||
{Region: "JP-JIS", Identifier: "JIS Z 8736-2:2014", Relation: "equivalent", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-13732-1", BatchID: "1a",
|
||||
Notes: "Ergonomics of the thermal environment — touchable hot surfaces.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 13732-1:2008-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASTM", Identifier: "ASTM C1055-20 (Hot-Surface Conditions)", Relation: "partial", Confidence: "medium"},
|
||||
{Region: "JP-JIS", Identifier: "JIS S 0033:2006", Relation: "equivalent", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-14122-1", BatchID: "1a",
|
||||
Notes: "Permanent means of access to machinery — choice of fixed means + general requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 14122-1:2016-10", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910 Subpart D (Walking-Working Surfaces)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI A1264.1-2017 (Walking/Working Surfaces)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 17888.1-2008", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-14122-2", BatchID: "1a",
|
||||
Notes: "Working platforms and walkways.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 14122-2:2016-10", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.28 (Duty to provide fall protection)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 17888.2-2008", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-14122-3", BatchID: "1a",
|
||||
Notes: "Stairs, stepladders, and guard-rails.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 14122-3:2016-10", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.25 (Stairways)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 17888.3-2008", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-19353", BatchID: "1a",
|
||||
Notes: "Fire prevention and fire protection for machinery.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 19353:2019-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 654 (Combustible Particulate Solids)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-842", BatchID: "1a",
|
||||
Notes: "Visual danger signals — safety of machinery.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 842:2009-01", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI Z535.4 (Product Safety Signs and Labels)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-7731", BatchID: "1a",
|
||||
Notes: "Danger signals for public and work areas — auditory.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 7731:2008-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "JP-JIS", Identifier: "JIS Z 8735:2000", Relation: "equivalent", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-894-1", BatchID: "1a",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 894-1:2009-02 (Ergonomic design of displays/control actuators)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 9355-1:1999 (Ergonomics — Displays and control actuators)", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-894-2", BatchID: "1a",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 894-2:2009-02 (Displays)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 9355-2:1999", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-894-3", BatchID: "1a",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 894-3:2010-01 (Control actuators)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 9355-3:2006", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "IEC-60529", BatchID: "1a",
|
||||
Notes: "IP code — Degrees of protection provided by enclosures.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60529:2014-09 (VDE 0470-1)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NEMA", Identifier: "NEMA 250 (Enclosures for Electrical Equipment)", Relation: "partial", Confidence: "high", Notes: "Cross-walk to IP exists but NEMA includes corrosion and ice."},
|
||||
{Region: "US-UL", Identifier: "UL 50E:2020", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 4208-2017", Relation: "equivalent", Confidence: "verified"},
|
||||
{Region: "JP-JIS", Identifier: "JIS C 0920:2003", Relation: "equivalent", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-11688-1", BatchID: "1a",
|
||||
Notes: "Acoustics — design of low-noise machinery, planning.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11688-1:2009-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-15534-1", BatchID: "1a",
|
||||
Notes: "Ergonomic design for safety of machinery — body dimensions through openings.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 15534-1:2000-09", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-11553-1", BatchID: "1a",
|
||||
Notes: "Safety of laser processing machines — general requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11553-1:2020-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI Z136.1-2022 (Safe Use of Lasers)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13478", BatchID: "1a",
|
||||
Notes: "Fire prevention and protection — general requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13478:2011-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-20607", BatchID: "1a",
|
||||
Notes: "Safety of machinery — instruction handbook (drafting principles).",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 20607:2019-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI Z535.6-2011 (R2017) (Product Safety Information in Manuals)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-61439-1", BatchID: "1a",
|
||||
Notes: "Low-voltage switchgear and controlgear assemblies — general rules.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 61439-1:2012-06 (VDE 0660-600-1)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 61439-1:2020", Relation: "equivalent", Confidence: "verified"},
|
||||
{Region: "US-UL", Identifier: "UL 891 (Switchboards)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-62311", BatchID: "1a",
|
||||
Notes: "Assessment of human exposure to electromagnetic fields.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 62311:2008-11", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-FCC", Identifier: "FCC OET-65 / 47 CFR 1.1310", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "IEC-61508-1", BatchID: "1a",
|
||||
Notes: "Functional safety of E/E/PE safety-related systems — general requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 61508-1:2011-02 (VDE 0803-1)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ISA-61508-1:2010", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 20438.1-2017", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS C 0508-1:2012", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "IEC-61508-2", BatchID: "1a",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 61508-2:2011-02 (VDE 0803-2)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ISA-61508-2:2010", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 20438.2-2017", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "IEC-61508-3", BatchID: "1a",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 61508-3:2011-02 (VDE 0803-3)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ISA-61508-3:2010", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 20438.3-2017", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-5349-1", BatchID: "1a",
|
||||
Notes: "Mechanical vibration — measurement of hand-transmitted vibration.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 5349-1:2001-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI S2.70-2006 (R2020) (Hand-Arm Vibration)", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS B 7761-1:2017", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-2631-1", BatchID: "1a",
|
||||
Notes: "Mechanical vibration — whole-body vibration.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 2631-1:2010-05", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI S3.18-2002 (R2017) (Whole-Body Vibration)", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS B 7760-2:2004", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-3744", BatchID: "1a",
|
||||
Notes: "Determination of sound power levels — engineering method, essentially-free field.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 3744:2011-02", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI S12.54-2011 (R2021)", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS Z 8734:2000", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-3746", BatchID: "1a",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 3746:2011-03", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI S12.56-2011", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-11689", BatchID: "1a",
|
||||
Notes: "Acoustics — procedure for comparing noise-emission data for machinery.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11689:1997-01", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-11228-2", BatchID: "1a",
|
||||
Notes: "Ergonomics — pushing and pulling.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1005-3:2009 / DIN EN ISO 11228-2:2007", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-OSHA", Identifier: "Snook & Ciriello Push-Pull Tables (Liberty Mutual)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-11228-3", BatchID: "1a",
|
||||
Notes: "Ergonomics — handling of low loads at high frequency.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1005-5:2007 / DIN EN ISO 11228-3:2007", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-OSHA", Identifier: "ACGIH TLV for HAL (Hand Activity Level)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1005-1", BatchID: "1a",
|
||||
Notes: "Human physical performance — terms and definitions. Now harmonized into ISO 11228 family.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1005-1:2009-01", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 11228 family", Relation: "supersedes", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1005-2", BatchID: "1a",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1005-2:2009-04 (Manual handling)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 11228-1:2021", Relation: "supersedes", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1005-3", BatchID: "1a",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1005-3:2009-01 (Recommended force limits)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 11228-2:2007", Relation: "supersedes", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1005-4", BatchID: "1a",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1005-4:2009-01 (Working postures)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 11226:2000", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-13732-3", BatchID: "1a",
|
||||
Notes: "Ergonomics of the thermal environment — cold surfaces.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 13732-3:2008-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
package iace
|
||||
|
||||
// Cross-reference matrix — Batch 1b (IDs 51-100 in norms_library.go load order).
|
||||
// Covers remaining B2-norms (ATEX, EMC, ergonomics, cybersecurity) and the
|
||||
// first wave of C-norms (presses, robots, conveyors, plastics machinery).
|
||||
// C-norm international equivalents are less harmonized than A/B norms;
|
||||
// confidence levels reflect this.
|
||||
|
||||
func init() {
|
||||
registerCrossRefs(batch1bCrossRefs())
|
||||
}
|
||||
|
||||
// batch1bCrossRefs contains entries 51-100.
|
||||
func batch1bCrossRefs() []NormCrossRef {
|
||||
return []NormCrossRef{
|
||||
{
|
||||
NormID: "EN-1127-1", BatchID: "1b",
|
||||
Notes: "Explosive atmospheres — explosion prevention and protection (ATEX).",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1127-1:2019-10", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 69:2024 (Explosion Prevention Systems)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 654 (Combustible Dust)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.307 (Hazardous (classified) locations)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13463-1", BatchID: "1b",
|
||||
Notes: "Non-electrical equipment for explosive atmospheres. Largely superseded by EN ISO 80079-36/-37.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13463-1:2009-07 (withdrawn 2018)", Relation: "superseded_by", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 80079-36:2016 / ISO 80079-37:2016", Relation: "supersedes", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-4021", BatchID: "1b",
|
||||
Notes: "Hydraulic fluid power — extraction of fluid samples for contamination analysis.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN ISO 4021:2017-09", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-982", BatchID: "1b",
|
||||
Notes: "Hydraulic safety — withdrawn, replaced by EN ISO 4413.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 982:1996+A1:2008 (withdrawn 2010)", Relation: "superseded_by", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 4413:2010", Relation: "supersedes", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-983", BatchID: "1b",
|
||||
Notes: "Pneumatic safety — withdrawn, replaced by EN ISO 4414.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 983:1996+A1:2008 (withdrawn 2010)", Relation: "superseded_by", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 4414:2010", Relation: "supersedes", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-14118", BatchID: "1b",
|
||||
Notes: "Prevention of unexpected start-up (formerly EN 1037).",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 14118:2018-06", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.147 (LOTO)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ASSP Z244.1-2016 (Lockout/Tagout)", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 19670-2005", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-574", BatchID: "1b",
|
||||
Notes: "Two-hand control devices — functional aspects and design principles.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 13851:2019-12 (replaces EN 574)", Relation: "superseded_by", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 13851:2019", Relation: "supersedes", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.19-2019 §10 (Two-Hand Control)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.217(c)(3)(iii)(c) (Press Two-Hand Trip)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "IEC-62443-4-2", BatchID: "1b",
|
||||
Notes: "Industrial Automation and Control Systems (IACS) cybersecurity — component requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN IEC 62443-4-2:2020-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ISA-62443-4-2-2018", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 33009.1-2016 (IACS Cybersecurity)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "IEC-62443-3-3", BatchID: "1b",
|
||||
Notes: "IACS cybersecurity — system security requirements and security levels.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN IEC 62443-3-3:2020-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ISA-62443-3-3-2013", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12198-1", BatchID: "1b",
|
||||
Notes: "Safety of machinery — assessment and reduction of risks arising from radiation.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12198-1:2009-07", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-626-1", BatchID: "1b",
|
||||
Notes: "Reduction of risk to health from hazardous substances emitted by machinery — Part 1: principles.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 626-1:2008-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.1000 (Air Contaminants PELs)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-626-2", BatchID: "1b",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 626-2:2008-09 (Verification procedure)", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-61000-6-1", BatchID: "1b",
|
||||
Notes: "EMC — Generic immunity for residential, commercial, light-industry environments.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 61000-6-1:2019-11 (VDE 0839-6-1)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 61000-6-1:2016", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-FCC", Identifier: "47 CFR Part 15 Subpart B", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 17799.1-2017", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-61000-6-2", BatchID: "1b",
|
||||
Notes: "EMC — Generic immunity for industrial environments.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 61000-6-2:2019-11 (VDE 0839-6-2)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 61000-6-2:2016", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 17799.2-2003", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-61000-6-3", BatchID: "1b",
|
||||
Notes: "EMC — Generic emission for residential/commercial environments.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 61000-6-3:2022-04 (VDE 0839-6-3)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 61000-6-3:2020", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-FCC", Identifier: "47 CFR Part 15 Subpart B", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 17799.3-2012", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-61000-6-4", BatchID: "1b",
|
||||
Notes: "EMC — Generic emission for industrial environments.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 61000-6-4:2020-09 (VDE 0839-6-4)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 61000-6-4:2018", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "CN-GB", Identifier: "GB 17799.4-2012", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-62353", BatchID: "1b",
|
||||
Notes: "Medical electrical equipment — recurrent test and test after repair.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 62353:2015-10 (VDE 0751-1)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 99:2024 §10 (Medical Equipment)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-50110-1", BatchID: "1b",
|
||||
Notes: "Operation of electrical installations — general requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 50110-1:2014-02 (VDE 0105-100)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 70E:2024 (Electrical Safety in the Workplace)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910 Subpart S (Electrical)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-60079-0", BatchID: "1b",
|
||||
Notes: "Explosive atmospheres (ATEX) — equipment, general requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN IEC 60079-0:2019-09 (VDE 0170-1)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 60079-0:2017", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-UL", Identifier: "UL 60079-0:2020", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "US-FM", Identifier: "FM 3600 (HazLoc Equipment General Requirements)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 3836.1-2021", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-60079-1", BatchID: "1b",
|
||||
Notes: "Equipment protection by flameproof enclosures 'd'.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60079-1:2014-06 (VDE 0170-5)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-UL", Identifier: "UL 60079-1:2020", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 3836.2-2021", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-60079-7", BatchID: "1b",
|
||||
Notes: "Equipment protection by increased safety 'e'.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60079-7:2016-04 (VDE 0170-6)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-UL", Identifier: "UL 60079-7:2017", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 3836.3-2021", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-60079-11", BatchID: "1b",
|
||||
Notes: "Equipment protection by intrinsic safety 'i'.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60079-11:2012-06 (VDE 0170-7)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-UL", Identifier: "UL 60079-11:2014", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 3836.4-2021", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-60079-14", BatchID: "1b",
|
||||
Notes: "Electrical installations design, selection, and erection in hazardous areas.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60079-14:2014-10 (VDE 0165-1)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 70 (NEC) Articles 500-506 (Hazardous Locations)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 3836.15-2017", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-60079-17", BatchID: "1b",
|
||||
Notes: "Inspection and maintenance of EX installations.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60079-17:2014-10 (VDE 0165-10-1)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 3836.16-2017", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-7000", BatchID: "1b",
|
||||
Notes: "Graphical symbols for use on equipment — registered symbols.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 80416 / DIN ISO 7000", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI Z535.3 (Criteria for Safety Symbols)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-7010", BatchID: "1b",
|
||||
Notes: "Graphical symbols — safety colours and signs, registered safety signs.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 7010:2020-07", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI Z535.2 (Environmental and Facility Safety Signs)", Relation: "partial", Confidence: "high", Notes: "US uses different colour/format conventions (signal words)."},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.145 (Specifications for accident prevention signs and tags)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS Z 9098:2016", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-61310-1", BatchID: "1b",
|
||||
Notes: "Indication, marking and actuation — Part 1: visual, auditory and tactile signals.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN IEC 61310-1:2017-08 (VDE 0113-101)", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-61310-2", BatchID: "1b",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN IEC 61310-2:2008-09 (Marking)", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-61310-3", BatchID: "1b",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 61310-3:2008-09 (Actuator location/operation)", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "IEC-61511-1", BatchID: "1b",
|
||||
Notes: "Functional safety — safety instrumented systems for the process industry sector.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 61511-1:2018-12 (VDE 0810-1)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ISA-61511-1-2018", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 21109.1-2007", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "IEC-61511-2", BatchID: "1b",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 61511-2:2018-12 (VDE 0810-2)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ISA-61511-2-2018", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "IEC-61511-3", BatchID: "1b",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 61511-3:2018-12 (VDE 0810-3)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ISA-61511-3-2018", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-692", BatchID: "1b",
|
||||
Notes: "Machine tools — mechanical presses — safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 692:2009-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.217 (Mechanical Power Presses)", Relation: "partial", Confidence: "high", Notes: "OSHA is the primary US requirement for mechanical presses."},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.1-2009 (R2020) (Mechanical Power Presses)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 17120-2012", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-693", BatchID: "1b",
|
||||
Notes: "Machine tools — hydraulic presses — safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 693:2019-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.2-2013 (R2020) (Hydraulic Power Presses)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 28241-2012", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12622", BatchID: "1b",
|
||||
Notes: "Machine tools — hydraulic press brakes — safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12622:2014-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.3-2012 (R2017) (Power Press Brakes)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-10218-1", BatchID: "1b",
|
||||
Notes: "Industrial robots — safety, robot manipulator. Updated 2025 edition exists.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 10218-1:2012-01", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/RIA R15.06-2012 (Part 1)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "CN-GB", Identifier: "GB 11291.1-2011", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS B 8433-1:2015", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-10218-2", BatchID: "1b",
|
||||
Notes: "Industrial robots — safety, integration. 2025 edition expands collaborative section.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 10218-2:2012-06", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/RIA R15.06-2012 (Part 2)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "CN-GB", Identifier: "GB 11291.2-2013", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS B 8433-2:2015", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-TS-15066", BatchID: "1b",
|
||||
Notes: "Collaborative robots — safety requirements (Technical Specification).",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN ISO/TS 15066:2017-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/RIA TR R15.606-2016", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-619", BatchID: "1b",
|
||||
Notes: "Continuous handling equipment — packs and individual loads.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 619:2022-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B20.1-2021 (Conveyor Safety)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1926.555 (Conveyors)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-620", BatchID: "1b",
|
||||
Notes: "Continuous handling equipment — belt conveyors for bulk materials.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 620:2022-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/CEMA B20.1-2021", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 10595-2017", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-349", BatchID: "1b",
|
||||
Notes: "Minimum gaps to avoid crushing parts of the human body.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 13854:2020-04 (replaces EN 349)", Relation: "superseded_by", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 13854:2017", Relation: "supersedes", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.19-2019 §C.1 (Minimum clearance distances)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 12265.3-1997 (now GB/T 23820-2009)", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-953", BatchID: "1b",
|
||||
Notes: "Guards — withdrawn, replaced by EN ISO 14120.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 953:2009-08 (withdrawn 2017)", Relation: "superseded_by", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 14120:2015", Relation: "supersedes", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-11161", BatchID: "1b",
|
||||
Notes: "Safety of machinery — integrated manufacturing systems, basic requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11161:2010-05", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.20-2017 (Manufacturing Systems)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 19891-2005", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1010-1", BatchID: "1b",
|
||||
Notes: "Printing and paper-converting machines — common requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1010-1:2011-03", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B65.1-2011 (Printing Press Systems)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12417", BatchID: "1b",
|
||||
Notes: "Machine tools — machining centres safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12417:2009-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.22-2002 (R2020) (Numerically Controlled Turning Machines)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "IEC-61800-5-2", BatchID: "1b",
|
||||
Notes: "Adjustable speed electrical power drive systems — functional safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 61800-5-2:2018-08 (VDE 0160-105-2)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-UL", Identifier: "UL 61800-5-1:2020", Relation: "partial", Confidence: "medium", Notes: "UL covers Part 5-1 (general safety); 5-2 functional safety often referenced directly."},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-201", BatchID: "1b",
|
||||
Notes: "Plastics and rubber machines — injection moulding machines safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 201:2010-03", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B151.1-2017 (Injection Moulding Machines)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 22530-2008", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-289", BatchID: "1b",
|
||||
Notes: "Plastics and rubber machines — compression and transfer moulding machines safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 289:2014-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B151.27 (Compression Moulding)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-422", BatchID: "1b",
|
||||
Notes: "Plastics and rubber machines — blow-moulding machines safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 422:2009-11", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B151.15 (Blow Moulding)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1114-1", BatchID: "1b",
|
||||
Notes: "Plastics and rubber machines — extruders and extrusion lines safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1114-1:2011-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B151.21 (Extrusion Blow Moulding)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-848-1", BatchID: "1b",
|
||||
Notes: "Safety of woodworking machines — single-spindle vertical moulding machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 848-1:2017-11", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI O1.1-2019 (Woodworking Machinery)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.213 (Woodworking machinery)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
package iace
|
||||
|
||||
// Cross-reference matrix — Batch 2a (IDs 101-150).
|
||||
// Covers C-norms for woodworking machines, pressure machines, packaging
|
||||
// machines (EN 415 series), and food-processing machines. Many are
|
||||
// EU-specific C-norms; international equivalents are partial at best.
|
||||
|
||||
func init() {
|
||||
registerCrossRefs(batch2aCrossRefs())
|
||||
}
|
||||
|
||||
func batch2aCrossRefs() []NormCrossRef {
|
||||
return []NormCrossRef{
|
||||
{
|
||||
NormID: "EN-1870-1", BatchID: "2a",
|
||||
Notes: "Woodworking machines — circular sawing machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1870-1:2014-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI O1.1-2019 (Woodworking Machinery)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.213 (Woodworking machinery)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-861", BatchID: "2a",
|
||||
Notes: "Woodworking machines — surface planing/thicknessing combined machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 861:2007-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.213(g) (Planers)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12840", BatchID: "2a",
|
||||
Notes: "Woodworking machines — hand-fed and/or hand-removed engraving machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12840:2009-05", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13128", BatchID: "2a",
|
||||
Notes: "Machine tools — milling machines safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13128:2009-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.8-2001 (R2017) (Manual Milling, Drilling, Boring)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13218", BatchID: "2a",
|
||||
Notes: "Machine tools — stationary grinding machines safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13218:2009-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.9-2010 (R2020) (Grinding Machines)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.215 (Abrasive wheel machinery)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-16092-1", BatchID: "2a",
|
||||
Notes: "Machine tools safety — presses, Part 1: general safety requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 16092-1:2018-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.0/B11.TR3 (Press family general)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-16092-3", BatchID: "2a",
|
||||
Notes: "Machine tools safety — presses, Part 3: hydraulic presses safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 16092-3:2018-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.2-2013 (R2020) (Hydraulic Power Presses)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-415-1", BatchID: "2a",
|
||||
Notes: "Safety of packaging machines — Part 1: terminology and classification.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 415-1:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/PMMI B155.1-2016 (Packaging Machinery)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-415-5", BatchID: "2a",
|
||||
Notes: "Safety of packaging machines — Part 5: wrapping machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 415-5:2010-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/PMMI B155.1-2016", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1672-2", BatchID: "2a",
|
||||
Notes: "Food processing machinery — hygiene requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1672-2:2009-07", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NSF", Identifier: "NSF/ANSI/3-A 14159-1 (Hygienic Food Equipment)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-FDA", Identifier: "21 CFR 110 (Current Good Manufacturing Practice)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-617", BatchID: "2a",
|
||||
Notes: "Continuous handling equipment and systems — safety, storage in silos/bunkers.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 617:2010-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-618", BatchID: "2a",
|
||||
Notes: "Continuous handling equipment — safety, bulk handling equipment except fixed belt conveyors.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 618:2011-02", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B20.1-2021 (Conveyor Safety)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-474-1", BatchID: "2a",
|
||||
Notes: "Earth-moving machinery — safety, Part 1: general requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 474-1:2022-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ITSDF B56 series + SAE J1166/J1455", Relation: "partial", Confidence: "medium"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1926.602 (Material handling equipment)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 25684 series", Relation: "equivalent", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1726-1", BatchID: "2a",
|
||||
Notes: "Industrial trucks — safety, Part 1: self-propelled trucks up to 10 000 kg.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1726-1:1999-04 (now ISO 3691-1)", Relation: "superseded_by", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 3691-1:2015 (now harmonized as EN ISO 3691-1)", Relation: "supersedes", Confidence: "verified"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.178 (Powered industrial trucks)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ITSDF B56.1-2020 (Low-/High-Lift Trucks)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-15011", BatchID: "2a",
|
||||
Notes: "Cranes — bridge and gantry cranes.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 15011:2014-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME B30.2-2022 (Overhead and Gantry Cranes)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.179 (Overhead and gantry cranes)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-14492-1", BatchID: "2a",
|
||||
Notes: "Cranes — power-driven winches and hoists. Part 1: winches.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14492-1:2019-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME B30.7-2016 (Winches)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-60974-1", BatchID: "2a",
|
||||
Notes: "Arc welding equipment — Part 1: welding power sources.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60974-1:2019-07 (VDE 0544-1)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 60974-1:2017", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-UL", Identifier: "UL 551-2010 (Transformer-type arc-welding machines)", Relation: "partial", Confidence: "medium"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI Z49.1-2021 (Safety in Welding, Cutting)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 15579.1-2013", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1010-2", BatchID: "2a",
|
||||
Notes: "Printing/paper-converting machines — Part 2: printing/varnishing machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1010-2:2011-03", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B65.1-2011 (Printing Press Systems)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-809", BatchID: "2a",
|
||||
Notes: "Pumps and pump units for liquids — common safety requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 809:2012-10", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/HI Pump Standards (B73, B74, etc.)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1012-1", BatchID: "2a",
|
||||
Notes: "Compressors and vacuum pumps — safety, Part 1: compressors.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1012-1:2011-02", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME B19.1-2017 (Compressor Safety)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-11111-1", BatchID: "2a",
|
||||
Notes: "Safety requirements for textile machinery — Part 1: common requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11111-1:2017-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 11111-1:2016", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 36316-2018 (textile machinery safety)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-710", BatchID: "2a",
|
||||
Notes: "Foundry machinery — moulding and core-making machinery.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 710:2005-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI Z241.1-2017 (Sand Preparation, Moulding, Coremaking)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-869", BatchID: "2a",
|
||||
Notes: "Safety requirements for high pressure metal die casting units.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 869:2010-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI Z241.2-2017 (Melting and Pouring)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-81-20", BatchID: "2a",
|
||||
Notes: "Safety rules for the construction and installation of lifts — Part 20: passenger lifts.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 81-20:2020-06", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME A17.1-2022 (Safety Code for Elevators and Escalators)", Relation: "partial", Confidence: "high", Notes: "EU/US lift codes differ significantly in details; consult specialist."},
|
||||
{Region: "US-ANSI", Identifier: "ANSI A17.1 = ASME A17.1 (joint standard)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 7588.1-2020", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS A 4302:2006", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-4254-1", BatchID: "2a",
|
||||
Notes: "Agricultural machinery — safety, Part 1: general requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4254-1:2016-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ASABE S390.5 (Agricultural Machinery Safety)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1928 (Agriculture)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 10395.1-2009", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12547", BatchID: "2a",
|
||||
Notes: "Centrifuges — common safety requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12547:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1539", BatchID: "2a",
|
||||
Notes: "Dryers and ovens, in which flammable substances are released — safety requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1539:2015-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 86:2023 (Ovens and Furnaces)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1678", BatchID: "2a",
|
||||
Notes: "Food processing machinery — vegetable cutting machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1678+A1:2010-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/NSF 8 (Commercial Powered Food Preparation Equipment)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1612-1", BatchID: "2a",
|
||||
Notes: "Plastics and rubber machines — reaction moulding machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1612-1:2010-03", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-746-1", BatchID: "2a",
|
||||
Notes: "Industrial thermoprocessing equipment — general safety requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 746-1:2015-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 86:2023 (Ovens and Furnaces)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-746-2", BatchID: "2a",
|
||||
Notes: "Industrial thermoprocessing — fuel-fired equipment safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 746-2:2010-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 86:2023 §6 (Class B Ovens)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-453", BatchID: "2a",
|
||||
Notes: "Food processing machinery — dough mixers.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 453:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NSF", Identifier: "NSF/ANSI 8 (Powered Food Preparation Equipment)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1010-3", BatchID: "2a",
|
||||
Notes: "Printing/paper-converting machines — Part 3: cutting machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1010-3:2009-11", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-13851", BatchID: "2a",
|
||||
Notes: "Two-hand control devices — functional aspects and design (succeeds EN 574).",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 13851:2019-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 13851:2019", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.19-2019 §10", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1672-1", BatchID: "2a",
|
||||
Notes: "Food processing machinery — Part 1: terminology.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1672-1:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13389", BatchID: "2a",
|
||||
Notes: "Food processing machinery — mixers with horizontal shafts.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13389:2017-08", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13886", BatchID: "2a",
|
||||
Notes: "Food processing machinery — boiling pans with mechanical agitator/mixer.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13886:2017-08", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12042", BatchID: "2a",
|
||||
Notes: "Food processing machinery — automatic dough dividers.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12042:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12331", BatchID: "2a",
|
||||
Notes: "Food processing machinery — mincing machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12331:2017-08", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12855", BatchID: "2a",
|
||||
Notes: "Food processing machinery — rotary bowl cutters.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12855:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13570", BatchID: "2a",
|
||||
Notes: "Food processing machinery — mixing machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13570:2017-08", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13591", BatchID: "2a",
|
||||
Notes: "Food processing machinery — fixed deck oven loaders.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13591:2017-08", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-14655", BatchID: "2a",
|
||||
Notes: "Food processing machinery — baguette slicers.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14655:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13954", BatchID: "2a",
|
||||
Notes: "Food processing machinery — bread slicers.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13954:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12463", BatchID: "2a",
|
||||
Notes: "Food processing machinery — filling machines and auxiliary equipment.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12463:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12984", BatchID: "2a",
|
||||
Notes: "Food processing machinery — portable/hand-guided machines with mechanically driven cutting tools.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12984:2010-09", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-415-2", BatchID: "2a",
|
||||
Notes: "Safety of packaging machines — Part 2: pre-formed rigid container machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 415-2:2000-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/PMMI B155.1-2016", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-415-3", BatchID: "2a",
|
||||
Notes: "Safety of packaging machines — Part 3: form, fill, seal machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 415-3:2021-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/PMMI B155.1-2016", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-415-4", BatchID: "2a",
|
||||
Notes: "Safety of packaging machines — Part 4: palletisers and depalletisers.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 415-4:1999-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/PMMI B155.1-2016", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-415-6", BatchID: "2a",
|
||||
Notes: "Safety of packaging machines — Part 6: pallet wrapping machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 415-6:2013-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/PMMI B155.1-2016", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-415-7", BatchID: "2a",
|
||||
Notes: "Safety of packaging machines — Part 7: group and secondary packaging machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 415-7:2010-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/PMMI B155.1-2016", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
package iace
|
||||
|
||||
// Cross-reference matrix — Batch 2b (IDs 151-200).
|
||||
// Covers remainder of packaging machines (EN 415 series), textile machinery
|
||||
// (EN ISO 11111 family), agricultural machines (ISO 4254 family), earth-
|
||||
// moving (EN 474), cranes, lifts (EN 81 family), industrial trucks, and
|
||||
// pressure equipment. Many EU-specific; ANSI/OSHA equivalents are partial.
|
||||
|
||||
func init() {
|
||||
registerCrossRefs(batch2bCrossRefs())
|
||||
}
|
||||
|
||||
func batch2bCrossRefs() []NormCrossRef {
|
||||
return []NormCrossRef{
|
||||
{
|
||||
NormID: "EN-415-8", BatchID: "2b",
|
||||
Notes: "Safety of packaging machines — Part 8: strapping machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 415-8:2008-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/PMMI B155.1-2016", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-415-9", BatchID: "2b",
|
||||
Notes: "Safety of packaging machines — Part 9: noise measurement methods.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 415-9:2010-04", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-415-10", BatchID: "2b",
|
||||
Notes: "Safety of packaging machines — Part 10: general requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 415-10:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/PMMI B155.1-2016", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-11111-2", BatchID: "2b",
|
||||
Notes: "Textile machinery — spinning preparatory machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11111-2:2005-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 11111-2:2005", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-11111-3", BatchID: "2b",
|
||||
Notes: "Textile machinery — nonwoven machinery.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11111-3:2005-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 11111-3:2005", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-11111-4", BatchID: "2b",
|
||||
Notes: "Textile machinery — yarn processing.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11111-4:2005-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 11111-4:2005", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-11111-5", BatchID: "2b",
|
||||
Notes: "Textile machinery — fabric formation machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11111-5:2005-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 11111-5:2005", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-11111-6", BatchID: "2b",
|
||||
Notes: "Textile machinery — fabric finishing machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11111-6:2005-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 11111-6:2005", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-11111-7", BatchID: "2b",
|
||||
Notes: "Textile machinery — dyeing/finishing machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11111-7:2005-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 11111-7:2005", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-4254-5", BatchID: "2b",
|
||||
Notes: "Agricultural machinery — power-driven soil-working machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4254-5:2018-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ASABE S390.5 (Agricultural Machinery Safety)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 10395.5-2013", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-4254-6", BatchID: "2b",
|
||||
Notes: "Agricultural machinery — sprayers and liquid fertiliser distributors.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4254-6:2020-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "CN-GB", Identifier: "GB 10395.6-2006", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-4254-7", BatchID: "2b",
|
||||
Notes: "Agricultural machinery — combine harvesters, forage and cotton harvesters.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4254-7:2017-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "CN-GB", Identifier: "GB 10395.7-2006", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-4254-12", BatchID: "2b",
|
||||
Notes: "Agricultural machinery — rotary disc mowers, drum mowers, flail mowers.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4254-12:2017-09", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "ISO-4254-14", BatchID: "2b",
|
||||
Notes: "Agricultural machinery — wrap-baling machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4254-14:2016-04", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-474-2", BatchID: "2b",
|
||||
Notes: "Earth-moving machinery — Part 2: tractor dozers.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 474-2:2022-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-474-3", BatchID: "2b",
|
||||
Notes: "Earth-moving machinery — Part 3: loaders.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 474-3:2022-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1926.602", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-474-5", BatchID: "2b",
|
||||
Notes: "Earth-moving machinery — Part 5: hydraulic excavators.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 474-5:2022-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1926.602", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-474-6", BatchID: "2b",
|
||||
Notes: "Earth-moving machinery — Part 6: dumpers.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 474-6:2022-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13000", BatchID: "2b",
|
||||
Notes: "Cranes — mobile cranes safety requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13000:2018-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME B30.5-2021 (Mobile and Locomotive Cranes)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1926.1400 (Cranes & Derricks in Construction)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-14439", BatchID: "2b",
|
||||
Notes: "Cranes — tower cranes safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14439:2010-05", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME B30.3-2019 (Tower Cranes)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13852-1", BatchID: "2b",
|
||||
Notes: "Cranes — offshore cranes, general purpose.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13852-1:2014-05", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 19927:2022 (Offshore cranes)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-14985", BatchID: "2b",
|
||||
Notes: "Cranes — slewing jib cranes.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14985:2012-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME B30.11-2019 (Monorails & Underhung Cranes)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-14492-2", BatchID: "2b",
|
||||
Notes: "Cranes — power-driven hoists.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14492-2:2019-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME B30.16-2017 (Overhead Hoists)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-81-50", BatchID: "2b",
|
||||
Notes: "Safety rules for the construction and installation of lifts — Part 50: design rules.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 81-50:2020-06", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME A17.1-2022", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-81-70", BatchID: "2b",
|
||||
Notes: "Safety rules for lifts — Part 70: accessibility to lifts for persons including persons with disability.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 81-70:2021-07", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ICC", Identifier: "ICC A117.1-2017 (Accessible Buildings)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-ADA", Identifier: "ADA Standards for Accessible Design (2010)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1808", BatchID: "2b",
|
||||
Notes: "Safety requirements for suspended access equipment.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1808:2015-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1926.451 (Scaffolds)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-280", BatchID: "2b",
|
||||
Notes: "Mobile elevating work platforms — design, calculation, safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 280:2022-01", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI A92.20-2018 (Mobile Elevating Work Platforms)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1926.453 (Aerial Lifts)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1570-1", BatchID: "2b",
|
||||
Notes: "Lifting tables — Part 1: lifting tables for loads up to and including two levels.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1570-1:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI MH29.1-2020 (Lift Tables)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-741", BatchID: "2b",
|
||||
Notes: "Continuous handling equipment — safety for bulk material pneumatic conveyors.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 741:2010-09", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-528", BatchID: "2b",
|
||||
Notes: "Rail-dependent storage and retrieval equipment safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 528:2021-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI MH16.3-2020 (Automated Storage Retrieval Systems)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1175", BatchID: "2b",
|
||||
Notes: "Industrial trucks — electrical/electronic requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1175:2020-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-UL", Identifier: "UL 583 (Electric Industrial Trucks)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1459", BatchID: "2b",
|
||||
Notes: "Industrial trucks — self-propelled variable-reach trucks.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1459-1:2017-07", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ITSDF B56.6-2016 (Rough Terrain Trucks)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12158-1", BatchID: "2b",
|
||||
Notes: "Builders hoists for goods — Part 1: hoists with accessible platforms.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12158-1:2021-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME A17.1 §25 (Material Lifts)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1417", BatchID: "2b",
|
||||
Notes: "Plastics and rubber machines — two-roll mills.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1417:2014-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B151.5 (Two-Roll Rubber Mills)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1114-3", BatchID: "2b",
|
||||
Notes: "Plastics and rubber machines — extruders/extrusion lines, Part 3: pelletizers.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1114-3:2002-08", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12013", BatchID: "2b",
|
||||
Notes: "Plastics and rubber machines — internal mixers.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12013:2018-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12409", BatchID: "2b",
|
||||
Notes: "Plastics and rubber machines — thermoforming machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12409:2014-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B151.39 (Thermoforming Machines)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13418", BatchID: "2b",
|
||||
Notes: "Plastics and rubber machines — winding machines for film/sheet.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13418:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12301", BatchID: "2b",
|
||||
Notes: "Plastics and rubber machines — calenders.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12301:2014-04", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-11611", BatchID: "2b",
|
||||
Notes: "Protective clothing for use in welding and allied processes.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11611:2016-07", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ISEA 105 (Hand Protection) + NFPA 70E", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-50504", BatchID: "2b",
|
||||
Notes: "Validation of arc welding equipment.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 50504:2009-04 (VDE 0544-200)", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1012-2", BatchID: "2b",
|
||||
Notes: "Compressors and vacuum pumps — safety, Part 2: vacuum pumps.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1012-2:2011-02", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13445-1", BatchID: "2b",
|
||||
Notes: "Unfired pressure vessels — Part 1: general.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13445-1:2021-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME BPVC Section VIII (Pressure Vessels)", Relation: "partial", Confidence: "high", Notes: "Substantive technical differences in calculation method (DBA vs DBF)."},
|
||||
{Region: "CN-GB", Identifier: "GB 150 series (Pressure Vessels)", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS B 8265:2017", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-14359", BatchID: "2b",
|
||||
Notes: "Gas-loaded accumulators for fluid power applications.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14359:2017-08", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12453", BatchID: "2b",
|
||||
Notes: "Industrial, commercial and garage doors and gates — safety in use of power-operated doors.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12453:2022-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-UL", Identifier: "UL 325-2017 (Doors, Drapery, Gates, Louvers, and Windows)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-ASTM", Identifier: "ASTM F2200-22 (Standard Specification for Automated Vehicular Gate Construction)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12978", BatchID: "2b",
|
||||
Notes: "Safety devices for power-operated doors and gates.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12978+A1:2010-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-UL", Identifier: "UL 325-2017", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12545", BatchID: "2b",
|
||||
Notes: "Footwear manufacturing machinery — common safety requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12545:2000-08", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1034-1", BatchID: "2b",
|
||||
Notes: "Safety requirements for paper-making and paper-finishing machines — Part 1: common requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-1:2021-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B65.1-2011", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1034-3", BatchID: "2b",
|
||||
Notes: "Safety requirements for paper-making — Part 3: winders and slitter-winders.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-3:2012-10", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
package iace
|
||||
|
||||
// Cross-reference matrix — Batch 3a (IDs 201-250).
|
||||
// Covers machining (woodworking EN ISO 19085, machine tools EN ISO 23125,
|
||||
// abrasives, hand-held power tools EN ISO 11148), conveyors + automation
|
||||
// (industrial trucks EN ISO 3691 family, escalators EN 115), and some
|
||||
// service-lift specials (EN 81-31/41/43).
|
||||
|
||||
func init() {
|
||||
registerCrossRefs(batch3aCrossRefs())
|
||||
}
|
||||
|
||||
func batch3aCrossRefs() []NormCrossRef {
|
||||
return []NormCrossRef{
|
||||
{
|
||||
NormID: "EN-1034-4", BatchID: "3a",
|
||||
Notes: "Paper-making machines — Part 4: pulpers and their loading equipment.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-4:2021-08", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12413", BatchID: "3a",
|
||||
Notes: "Safety requirements for bonded abrasive products.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12413:2019-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B7.1-2017 (Safety Requirements for Abrasive Wheels)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.215", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13236", BatchID: "3a",
|
||||
Notes: "Safety requirements for superabrasive products.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13236:2019-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B7.1-2017", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-28881", BatchID: "3a",
|
||||
Notes: "Machine tools safety — electro-discharge machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 28881:2022-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 28881:2022", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-11553-2", BatchID: "3a",
|
||||
Notes: "Safety of laser processing machines — Part 2: hand-held laser processing devices.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11553-2:2019-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI Z136.1-2022 (Lasers)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-11553-3", BatchID: "3a",
|
||||
Notes: "Safety of laser processing machines — Part 3: noise reduction.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11553-3:2013-07", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-16092-2", BatchID: "3a",
|
||||
Notes: "Machine tools — presses, Part 2: mechanical presses safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 16092-2:2020-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.1-2009 (R2020) (Mechanical Power Presses)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.217", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-16092-4", BatchID: "3a",
|
||||
Notes: "Machine tools — presses, Part 4: pneumatic presses safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 16092-4:2020-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13736", BatchID: "3a",
|
||||
Notes: "Machine tools safety — pneumatic presses.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13736:2018-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1550", BatchID: "3a",
|
||||
Notes: "Machine tools safety — chucks for workholding.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1550+A1:2008-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.6 (Lathes) clauses on workholding", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-23125", BatchID: "3a",
|
||||
Notes: "Machine tools — turning machines safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 23125:2015-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.6-2001 (R2020) (Lathes)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.22-2002 (NC Turning Machines)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1807-1", BatchID: "3a",
|
||||
Notes: "Safety of woodworking machines — band saws, Part 1: table band saws.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1807-1:2013-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI O1.1-2019", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1807-2", BatchID: "3a",
|
||||
Notes: "Safety of woodworking machines — band saws, Part 2: log sawing.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1807-2:2013-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12921-1", BatchID: "3a",
|
||||
Notes: "Machines for surface cleaning/pre-treatment with liquids — Part 1: common safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12921-1:2009-04", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12921-2", BatchID: "3a",
|
||||
Notes: "Surface cleaning machines — Part 2: safety for machines using water-based liquids.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12921-2:2008-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12921-3", BatchID: "3a",
|
||||
Notes: "Surface cleaning machines — Part 3: safety for machines using flammable liquids.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12921-3:2017-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 30 (Flammable and Combustible Liquids)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12753", BatchID: "3a",
|
||||
Notes: "Thermal cleaning systems for components contaminated with organic substances.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12753:2018-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12215", BatchID: "3a",
|
||||
Notes: "Coating plants — spray booths for application of organic liquid coating materials.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12215:2018-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 33 (Spray Application Using Flammable or Combustible Materials)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.94(c) (Spray finishing operations)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13355", BatchID: "3a",
|
||||
Notes: "Coating plants — combined booths.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13355:2017-04", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1953", BatchID: "3a",
|
||||
Notes: "Atomising and spraying equipment for coating materials.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1953:2014-04", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-746-3", BatchID: "3a",
|
||||
Notes: "Industrial thermoprocessing — safety for atmosphere systems.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 746-3:2010-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 86:2023 §11 (Special Atmosphere Furnaces)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12464-1", BatchID: "3a",
|
||||
Notes: "Light and lighting — indoor work places.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12464-1:2021-11", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/IES RP-7 (Industrial Lighting)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-11148-1", BatchID: "3a",
|
||||
Notes: "Hand-held non-electric power tools — Part 1: assembly tools for threaded fasteners.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11148-1:2011-11", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 11148-1:2011", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-11148-3", BatchID: "3a",
|
||||
Notes: "Hand-held non-electric power tools — drills/tapping machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11148-3:2012-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 11148-3:2012", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-11148-6", BatchID: "3a",
|
||||
Notes: "Hand-held non-electric power tools — assembly power tools for threaded fasteners.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11148-6:2012-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 11148-6:2012", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-11148-10", BatchID: "3a",
|
||||
Notes: "Hand-held non-electric power tools — Part 10: portable abrasive tools.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11148-10:2017-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 11148-10:2017", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-52941", BatchID: "3a",
|
||||
Notes: "Additive manufacturing — performance of buildup equipment.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 52941:2021-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASTM", Identifier: "ASTM F3303-23 (Additive Manufacturing — Process Characteristics)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-52911-1", BatchID: "3a",
|
||||
Notes: "Additive manufacturing — design optimization for laser-based PBF.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO/ASTM 52911-1:2020-06", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASTM", Identifier: "ASTM F52911-19", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-19085-1", BatchID: "3a",
|
||||
Notes: "Woodworking machines safety — common requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 19085-1:2021-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI O1.1-2019", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.213", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-19085-5", BatchID: "3a",
|
||||
Notes: "Woodworking machines safety — Part 5: dimension saws.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 19085-5:2017-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.213(d) (Circular saws)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-621", BatchID: "3a",
|
||||
Notes: "Continuous handling equipment — special requirements for air-supported conveyors.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 621:2010-09", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-616", BatchID: "3a",
|
||||
Notes: "Continuous handling equipment — safety, mechanical/hydraulic feeders for paper rolls.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 616:2010-09", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-3691-4", BatchID: "3a",
|
||||
Notes: "Industrial trucks — safety, Part 4: driverless industrial trucks and their systems (AGVs).",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 3691-4:2023-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ITSDF B56.5-2019 (Driverless, Automatic Guided Industrial Vehicles)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 3691-4:2023", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1525", BatchID: "3a",
|
||||
Notes: "Safety of industrial trucks — driverless trucks (legacy; superseded by ISO 3691-4).",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1525:1998-01 (withdrawn 2020)", Relation: "superseded_by", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 3691-4:2023", Relation: "supersedes", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-15095", BatchID: "3a",
|
||||
Notes: "Power-operated mobile racking and shelving, carousels and storage lifts — safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 15095:2007-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI MH16.1-2021 (Industrial Steel Storage Racks)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13309", BatchID: "3a",
|
||||
Notes: "Construction machinery — electromagnetic compatibility (EMC).",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13309:2010-07", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12604", BatchID: "3a",
|
||||
Notes: "Industrial, commercial and garage doors and gates — mechanical aspects, requirements and test methods.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12604:2017-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-UL", Identifier: "UL 325-2017", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12635", BatchID: "3a",
|
||||
Notes: "Industrial, commercial and garage doors and gates — installation and use.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12635:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASTM", Identifier: "ASTM F2200-22 (Automated Vehicular Gate Construction)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-115-1", BatchID: "3a",
|
||||
Notes: "Safety of escalators and moving walks — construction and installation.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 115-1:2017-11", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME A17.1-2022 §6 (Escalators and Moving Walks)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 16899-2011", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS A 4302:2006 §B (Escalators)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-115-2", BatchID: "3a",
|
||||
Notes: "Safety of escalators and moving walks — Part 2: rules for improvement of safety of existing.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 115-2:2021-07", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME A17.3 (Safety Code for Existing Elevators)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-3691-1", BatchID: "3a",
|
||||
Notes: "Industrial trucks — safety, Part 1: self-propelled trucks (excludes AGVs).",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 3691-1:2015-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 3691-1:2015", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ITSDF B56.1-2020", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.178", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-3691-3", BatchID: "3a",
|
||||
Notes: "Industrial trucks — safety, Part 3: additional requirements for trucks with elevating operator position.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 3691-3:2016-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 3691-3:2016", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ITSDF B56.11.5", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-3691-5", BatchID: "3a",
|
||||
Notes: "Industrial trucks — safety, Part 5: pedestrian-propelled trucks.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 3691-5:2015-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 3691-5:2014", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-3691-6", BatchID: "3a",
|
||||
Notes: "Industrial trucks — safety, Part 6: burden and personnel carriers.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 3691-6:2015-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 3691-6:2013", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13135", BatchID: "3a",
|
||||
Notes: "Cranes — safety, design, requirements for equipment.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13135:2020-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12999", BatchID: "3a",
|
||||
Notes: "Cranes — loader cranes.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12999:2020-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME B30.22-2016 (Articulating Boom Cranes)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-14238", BatchID: "3a",
|
||||
Notes: "Cranes — manually controlled load manipulating devices.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14238:2010-05", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13157", BatchID: "3a",
|
||||
Notes: "Cranes — safety, hand-powered lifting equipment.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13157:2009-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME B30.21-2014 (Lever Hoists)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-14943", BatchID: "3a",
|
||||
Notes: "Transport services — terminal handling equipment for waste from inland waterway and sea vessels.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14943:2005-05", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-81-31", BatchID: "3a",
|
||||
Notes: "Safety rules for lifts — Part 31: accessible goods only lifts.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 81-31:2010-07", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,427 @@
|
||||
package iace
|
||||
|
||||
// Cross-reference matrix — Batch 3b (IDs 251-300).
|
||||
// Covers process safety (piping, boilers, pressure vessels EN 13480/12952/
|
||||
// 12953), pressure-related ISO standards, wind turbines (IEC 61400),
|
||||
// photovoltaics (IEC 62446), rotating electrical machinery (IEC 60034),
|
||||
// refrigeration, fuel-cell systems, large battery installations, and the
|
||||
// remainder of EN-474 construction equipment.
|
||||
|
||||
func init() {
|
||||
registerCrossRefs(batch3bCrossRefs())
|
||||
}
|
||||
|
||||
func batch3bCrossRefs() []NormCrossRef {
|
||||
return []NormCrossRef{
|
||||
{
|
||||
NormID: "EN-81-41", BatchID: "3b",
|
||||
Notes: "Safety rules for lifts — Part 41: vertical lifting platforms intended for use by persons with impaired mobility.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 81-41:2011-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME A18.1-2020 (Platform Lifts and Stairway Chairlifts)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-81-43", BatchID: "3b",
|
||||
Notes: "Safety rules for lifts — Part 43: lifts for cranes.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 81-43:2010-01", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1398", BatchID: "3b",
|
||||
Notes: "Dock levellers safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1398:2009-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI MH30.2-2015 (Dock Levellers)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1756-1", BatchID: "3b",
|
||||
Notes: "Tail lifts — Part 1: tail lifts for goods.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1756-1:2021-02", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI MH30.1-2015 (Truck Liftgates)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1756-2", BatchID: "3b",
|
||||
Notes: "Tail lifts — Part 2: tail lifts for persons.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1756-2:2009-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ADA", Identifier: "ADA Standards 2010 + DOT FMVSS 403", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13480-1", BatchID: "3b",
|
||||
Notes: "Metallic industrial piping — Part 1: general.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13480-1:2017-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME B31.3-2022 (Process Piping)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 20801 series", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13480-3", BatchID: "3b",
|
||||
Notes: "Metallic industrial piping — Part 3: design and calculation.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13480-3:2017-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME B31.3-2022 §300-305", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-764-7", BatchID: "3b",
|
||||
Notes: "Pressure equipment — safety systems for unfired pressure equipment.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 764-7:2002-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME BPVC Section VIII Div.1 §UG-125 (Overpressure)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12952-1", BatchID: "3b",
|
||||
Notes: "Water-tube boilers and auxiliary installations — Part 1: general.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12952-1:2015-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME BPVC Section I (Power Boilers)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 16507 series", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12953-1", BatchID: "3b",
|
||||
Notes: "Shell boilers — Part 1: general.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12953-1:2012-10", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME BPVC Section IV (Heating Boilers)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 16508 series", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-21049", BatchID: "3b",
|
||||
Notes: "Pumps — shaft sealing systems for centrifugal and rotary pumps.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 21049:2004-11", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-API", Identifier: "API 682 (Pumps — Shaft Sealing Systems)", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12162", BatchID: "3b",
|
||||
Notes: "Liquid pumps — safety, hydrostatic testing procedure.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12162:2009-07", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-14462", BatchID: "3b",
|
||||
Notes: "Surface treatment equipment — noise test code.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14462:2015-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12757-1", BatchID: "3b",
|
||||
Notes: "Mixing machinery for coating materials — Part 1: mixers for general application.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12757-1:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-50156-1", BatchID: "3b",
|
||||
Notes: "Electrical equipment for furnaces — Part 1: requirements for application design.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 50156-1:2016-03 (VDE 0116-1)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 85 (Boiler and Combustion Systems Hazards Code)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 86 (Ovens and Furnaces)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-14460", BatchID: "3b",
|
||||
Notes: "Explosion-resistant equipment.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14460:2018-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 69 (Explosion Prevention Systems)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-14644-1", BatchID: "3b",
|
||||
Notes: "Cleanrooms and associated controlled environments — Part 1: classification of air cleanliness.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 14644-1:2016-06", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 14644-1:2015", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-FDA", Identifier: "FDA cGMP (21 CFR 211, 21 CFR 820) + USP <797>", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-14644-4", BatchID: "3b",
|
||||
Notes: "Cleanrooms — Part 4: design, construction and start-up.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 14644-4:2022-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 14644-4:2022", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-14015", BatchID: "3b",
|
||||
Notes: "Specification for design and manufacture of site built, vertical, cylindrical, flat-bottomed, above-ground welded steel tanks.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14015:2005-02", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-API", Identifier: "API 650 (Welded Tanks for Oil Storage)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13094", BatchID: "3b",
|
||||
Notes: "Tanks for the transport of dangerous goods — metallic tanks with working pressure <= 0.5 bar.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13094:2020-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-DOT", Identifier: "49 CFR Part 178 (Specifications for Packagings)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-16767", BatchID: "3b",
|
||||
Notes: "Industrial valves — metallic check valves.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 16767:2016-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-API", Identifier: "API 594 (Check Valves)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-4126-1", BatchID: "3b",
|
||||
Notes: "Safety devices for protection against excessive pressure — Part 1: safety valves.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4126-1:2013-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 4126-1:2013", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME BPVC Section VIII §UG-126 (Pressure Relief)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-API", Identifier: "API 526 (Flanged Steel Pressure-Relief Valves)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-ISO-4126-4", BatchID: "3b",
|
||||
Notes: "Safety devices — Part 4: pilot-operated safety valves.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4126-4:2013-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-API", Identifier: "API 520 / API 526", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1854", BatchID: "3b",
|
||||
Notes: "Pressure-sensing devices for gas burners and gas-burning appliances.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1854:2010-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI Z21.21 / CSA 6.5 (Combustion Controls)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-161", BatchID: "3b",
|
||||
Notes: "Automatic shut-off valves for gas burners.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 161:2013-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI Z21.21 / CSA 6.5", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12566-3", BatchID: "3b",
|
||||
Notes: "Small wastewater treatment systems for up to 50 PT.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12566-3:2016-11", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NSF", Identifier: "NSF/ANSI 40 (Residential Wastewater Treatment Systems)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-14181", BatchID: "3b",
|
||||
Notes: "Stationary source emissions — quality assurance of automated measuring systems.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14181:2014-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-EPA", Identifier: "40 CFR Part 60 Appendix F (QA Procedures for CEMS)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-IEC-61400-1", BatchID: "3b",
|
||||
Notes: "Wind energy generation systems — Part 1: design requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN IEC 61400-1:2019-12 (VDE 0127-1)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 61400-1:2019", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ACP 61400-1-2021 (American Clean Power)", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 18451.1-2022", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-IEC-61400-2", BatchID: "3b",
|
||||
Notes: "Wind energy generation systems — Part 2: small wind turbines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN IEC 61400-2:2014-11 (VDE 0127-2)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 61400-2:2013", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ACP Small Wind Turbines (formerly AWEA 9.1)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-62446-1", BatchID: "3b",
|
||||
Notes: "Photovoltaic (PV) systems — requirements for testing, documentation and maintenance.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 62446-1:2017-04 (VDE 0126-23-1)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 62446-1:2016", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 70 (NEC) Article 690 (Solar Photovoltaic Systems)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-UL", Identifier: "UL 1741-2021 (Inverters for use with PV systems)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-60034-1", BatchID: "3b",
|
||||
Notes: "Rotating electrical machines — Part 1: rating and performance.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60034-1:2011-02 (VDE 0530-1)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 60034-1:2017", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NEMA", Identifier: "NEMA MG 1-2021 (Motors and Generators)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI C50.10 / C50.13 (Synchronous machines)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 755-2019", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS C 4034-1:2011", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-60034-5", BatchID: "3b",
|
||||
Notes: "Rotating electrical machines — Part 5: degrees of protection (IP code) for machines.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60034-5:2008-10 (VDE 0530-5)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 60034-5:2006", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NEMA", Identifier: "NEMA MG 1 §5 (Enclosures)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-14276-1", BatchID: "3b",
|
||||
Notes: "Pressure equipment for refrigerating systems and heat pumps — Part 1: vessels.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14276-1:2020-11", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME BPVC Section VIII Div.1 + ANSI/AHRI 495", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-ASHRAE", Identifier: "ASHRAE 15-2022 (Safety Standard for Refrigeration Systems)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-378-1", BatchID: "3b",
|
||||
Notes: "Refrigerating systems and heat pumps — safety and environmental requirements — Part 1: basic requirements.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 378-1:2021-06", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASHRAE", Identifier: "ASHRAE 15-2022", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-ASHRAE", Identifier: "ASHRAE 34-2022 (Refrigerant Classification)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12621", BatchID: "3b",
|
||||
Notes: "Machinery for the supply and circulation of coating materials under pressure.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12621:2014-10", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-14753", BatchID: "3b",
|
||||
Notes: "Safety requirements for machinery and plant for the continuous casting of steel.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14753:2007-04", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-12952-7", BatchID: "3b",
|
||||
Notes: "Water-tube boilers — Part 7: requirements for equipment for the boiler.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12952-7:2012-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME BPVC Section I", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-14917", BatchID: "3b",
|
||||
Notes: "Metal bellows expansion joints for pressure applications.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14917:2021-07", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-EJMA", Identifier: "EJMA Standards (Expansion Joint Manufacturers Association)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-62282-3-100", BatchID: "3b",
|
||||
Notes: "Fuel cell technologies — Part 3-100: stationary fuel cell power systems, safety.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN IEC 62282-3-100:2020-08 (VDE 0130-3-100)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 62282-3-100:2019", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/CSA FC1 (Stationary Fuel Cell Power Systems)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-13445-3", BatchID: "3b",
|
||||
Notes: "Unfired pressure vessels — Part 3: design.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13445-3:2021-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME BPVC Section VIII Div.1/Div.2 (Pressure Vessels)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 150.3-2011", Relation: "equivalent", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-62619", BatchID: "3b",
|
||||
Notes: "Secondary cells and batteries containing alkaline or non-acid electrolytes — safety requirements for secondary lithium cells/batteries for industrial applications.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN IEC 62619:2022-09 (VDE 0510-39)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 62619:2022", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-UL", Identifier: "UL 1973-2022 (Batteries for Stationary Applications)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 855-2023 (Stationary Energy Storage Systems)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-1991-4", BatchID: "3b",
|
||||
Notes: "Eurocode 1 — actions on structures — Part 4: silos and tanks.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1991-4:2010-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ACI", Identifier: "ACI 313 (Concrete Bins and Silos)", Relation: "partial", Confidence: "medium"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-15776", BatchID: "3b",
|
||||
Notes: "Unfired pressure vessels — requirements for the design and construction of pressure vessels made of cast iron.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 15776:2019-05", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-16282-1", BatchID: "3b",
|
||||
Notes: "Equipment for commercial kitchens — components for ventilation in commercial kitchens.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 16282-1:2017-10", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 96-2024 (Standard for Ventilation Control and Fire Protection of Commercial Cooking)", Relation: "partial", Confidence: "high"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-474-4", BatchID: "3b",
|
||||
Notes: "Earth-moving machinery — Part 4: backhoe loaders.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 474-4:2022-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-474-7", BatchID: "3b",
|
||||
Notes: "Earth-moving machinery — Part 7: scrapers.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 474-7:2022-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-474-8", BatchID: "3b",
|
||||
Notes: "Earth-moving machinery — Part 8: graders.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 474-8:2022-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-474-9", BatchID: "3b",
|
||||
Notes: "Earth-moving machinery — Part 9: pipelayers.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 474-9:2022-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-474-10", BatchID: "3b",
|
||||
Notes: "Earth-moving machinery — Part 10: trenchers.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 474-10:2022-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
{
|
||||
NormID: "EN-474-11", BatchID: "3b",
|
||||
Notes: "Earth-moving machinery — Part 11: earth and landfill compactors.",
|
||||
Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 474-11:2022-12", Relation: "identical", Confidence: "verified"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package iace
|
||||
|
||||
// Cross-reference matrix — Batch 4a (next 50, alphabetically sorted).
|
||||
// Covers paper machinery sub-parts (EN 1034-x), protective clothing
|
||||
// electrostatic (EN 1149-x), industrial trucks electrical (EN 1175-x),
|
||||
// playground equipment (EN 1176-x), and plastics granulators (EN 12012-x).
|
||||
// Many EU-specific C-norms; ANSI equivalents are partial or absent.
|
||||
|
||||
func init() {
|
||||
registerCrossRefs(batch4aCrossRefs())
|
||||
}
|
||||
|
||||
func batch4aCrossRefs() []NormCrossRef {
|
||||
return []NormCrossRef{
|
||||
{NormID: "EN-1010-4", BatchID: "4a", Notes: "Printing/paper-converting machines — Part 4: bookbinding, paper-converting and finishing machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1010-4:2007-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B65.5 (Bindery and Finishing Equipment)", Relation: "partial", Confidence: "medium"},
|
||||
}},
|
||||
{NormID: "EN-1012-3", BatchID: "4a", Notes: "Compressors and vacuum pumps — Part 3: process compressors.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1012-3:2014-06", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-API", Identifier: "API 617 (Axial and Centrifugal Compressors)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-1028-1", BatchID: "4a", Notes: "Fire-fighting pumps — fire-fighting centrifugal pumps with primer.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1028-1:2008-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 1901-2024 (Automotive Fire Apparatus)", Relation: "partial", Confidence: "medium"},
|
||||
}},
|
||||
{NormID: "EN-1034-2", BatchID: "4a", Notes: "Paper-making — Part 2: barking drums and debarking equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-2:2005-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1034-5", BatchID: "4a", Notes: "Paper-making — Part 5: sheeters.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-5:2010-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1034-6", BatchID: "4a", Notes: "Paper-making — Part 6: calenders.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-6:2012-10", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1034-7", BatchID: "4a", Notes: "Paper-making — Part 7: chests.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-7:2005-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1034-8", BatchID: "4a", Notes: "Paper-making — Part 8: refining plants.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-8:2012-10", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1034-9", BatchID: "4a", Notes: "Paper-making — Part 9: chemical mixers.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-9:2012-10", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1034-10", BatchID: "4a", Notes: "Paper-making — Part 10: coaters.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-10:2009-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1034-11", BatchID: "4a", Notes: "Paper-making — Part 11: tissue machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-11:2009-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1034-12", BatchID: "4a", Notes: "Paper-making — Part 12: cross cutters.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-12:2009-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1034-13", BatchID: "4a", Notes: "Paper-making — Part 13: machines for de-wiring of bales/units.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-13:2018-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1034-14", BatchID: "4a", Notes: "Paper-making — Part 14: reel splitter.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-14:2009-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1034-15", BatchID: "4a", Notes: "Paper-making — Part 15: sheet drying systems.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-15:2010-09", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1034-16", BatchID: "4a", Notes: "Paper-making — Part 16: paper and board machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-16:2012-10", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1034-17", BatchID: "4a", Notes: "Paper-making — Part 17: tissue making machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-17:2012-10", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1034-18", BatchID: "4a", Notes: "Paper-making — Part 18: pulper-feeding/discharging.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-18:2010-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1034-20", BatchID: "4a", Notes: "Paper-making — Part 20: roll calenders.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-20:2017-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1034-21", BatchID: "4a", Notes: "Paper-making — Part 21: coating machines (after-treatment).", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-21:2012-10", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1034-22", BatchID: "4a", Notes: "Paper-making — Part 22: wood grinders.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-22:2005-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1034-26", BatchID: "4a", Notes: "Paper-making — Part 26: machines for packaging of bobbins and reels.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-26:2017-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1034-27", BatchID: "4a", Notes: "Paper-making — Part 27: reel handling systems.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1034-27:2017-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1035", BatchID: "4a", Notes: "Conveyor belts — laboratory scale flammability characteristics.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1035 (withdrawn, see ISO 340)", Relation: "superseded_by", Confidence: "medium"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 340 (Conveyor belts — Laboratory scale flammability characteristics)", Relation: "supersedes", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-1036", BatchID: "4a", Notes: "Glass in building — mirrors from silver-coated float glass.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1036-1:2008-01", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1149-1", BatchID: "4a", Notes: "Protective clothing — electrostatic properties, Part 1: surface resistivity.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1149-1:2006-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ESD STM2.1 / ESD S20.20-2014", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 2113-2020 (Flame-Resistant Garments)", Relation: "partial", Confidence: "medium"},
|
||||
}},
|
||||
{NormID: "EN-1149-5", BatchID: "4a", Notes: "Protective clothing — electrostatic properties, Part 5: material performance and design requirements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1149-5:2018-06", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 2113-2020", Relation: "partial", Confidence: "medium"},
|
||||
}},
|
||||
{NormID: "EN-1175-1", BatchID: "4a", Notes: "Industrial trucks — electrical/electronic requirements, Part 1: trucks with battery (legacy).", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1175-1:2014-08 (withdrawn, see EN 1175:2020)", Relation: "superseded_by", Confidence: "verified"},
|
||||
{Region: "US-UL", Identifier: "UL 583 (Electric Industrial Trucks)", Relation: "partial", Confidence: "medium"},
|
||||
}},
|
||||
{NormID: "EN-1175-2", BatchID: "4a", Notes: "Industrial trucks — Part 2: internal combustion engine drive (legacy).", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1175-2:2014-08 (withdrawn)", Relation: "superseded_by", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1175-3", BatchID: "4a", Notes: "Industrial trucks — Part 3: electrical power transmission (legacy).", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1175-3:2014-08 (withdrawn)", Relation: "superseded_by", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1176-1", BatchID: "4a", Notes: "Playground equipment and surfacing — Part 1: general safety requirements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1176-1:2017-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASTM", Identifier: "ASTM F1487-21 (Public Use Playground Equipment)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-CPSC", Identifier: "CPSC Public Playground Safety Handbook 2010", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-1176-2", BatchID: "4a", Notes: "Playground equipment — Part 2: swings.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1176-2:2020-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASTM", Identifier: "ASTM F1487-21", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-1176-3", BatchID: "4a", Notes: "Playground equipment — Part 3: slides.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1176-3:2020-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASTM", Identifier: "ASTM F1487-21", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-1176-4", BatchID: "4a", Notes: "Playground equipment — Part 4: cableways.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1176-4:2017-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1176-5", BatchID: "4a", Notes: "Playground equipment — Part 5: carousels.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1176-5:2019-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1176-6", BatchID: "4a", Notes: "Playground equipment — Part 6: rocking equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1176-6:2017-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1176-7", BatchID: "4a", Notes: "Playground equipment — Part 7: guidance on installation, inspection, maintenance.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1176-7:2020-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASTM", Identifier: "ASTM F1487-21", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-12001", BatchID: "4a", Notes: "Conveying, spraying and placing machinery for concrete and mortar.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12001:2013-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ACPA M-1 (Concrete Pumping Safety)", Relation: "partial", Confidence: "medium"},
|
||||
}},
|
||||
{NormID: "EN-12012-1", BatchID: "4a", Notes: "Plastics and rubber machines — size-reduction machines, Part 1: blade granulators.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12012-1:2018-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12012-2", BatchID: "4a", Notes: "Plastics — size-reduction machines, Part 2: strand pelletizers.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12012-2:2018-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12012-3", BatchID: "4a", Notes: "Plastics — size-reduction machines, Part 3: shredders.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12012-3:2018-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12012-4", BatchID: "4a", Notes: "Plastics — size-reduction machines, Part 4: agglomerators.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12012-4:2018-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12041", BatchID: "4a", Notes: "Food processing machinery — moulders.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12041:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12043", BatchID: "4a", Notes: "Food processing machinery — intermediate provers.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12043:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12044", BatchID: "4a", Notes: "Food processing machinery — cutting and wrapping machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12044:2018-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12097", BatchID: "4a", Notes: "Ventilation for buildings — ductwork, requirements for ductwork components.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12097:2007-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-SMACNA", Identifier: "SMACNA HVAC Duct Construction Standards (Metal & Flexible)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-12111", BatchID: "4a", Notes: "Tunnelling machines — road headers, continuous miners and impact rippers.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12111:2014-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-OSHA", Identifier: "30 CFR Part 75 (Underground Coal Mines)", Relation: "partial", Confidence: "medium"},
|
||||
}},
|
||||
{NormID: "EN-12151", BatchID: "4a", Notes: "Machinery and plants for the preparation of concrete and mortar.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12151:2007-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12182", BatchID: "4a", Notes: "Assistive products for persons with disability — general requirements and test methods.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12182:2012-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 9999:2022 (Assistive products classification)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-12312-1", BatchID: "4a", Notes: "Aircraft ground support equipment — Part 1: general requirements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12312-1:2013-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-SAE", Identifier: "SAE ARP 1247 (General Requirements for Aerospace Ground Support Equipment)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-12312-2", BatchID: "4a", Notes: "Aircraft ground support — Part 2: catering vehicles.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12312-2:2014-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package iace
|
||||
|
||||
// Cross-reference matrix — Batch 4b (next 50, alphabetical).
|
||||
// Covers aircraft ground support equipment (EN 12312 series), steel wire
|
||||
// ropes (EN 12385 series), scaffolds (EN 12810/12811), cranes design
|
||||
// (EN 13001 series), and various boiler / cleaning niches.
|
||||
|
||||
func init() {
|
||||
registerCrossRefs(batch4bCrossRefs())
|
||||
}
|
||||
|
||||
func batch4bCrossRefs() []NormCrossRef {
|
||||
return []NormCrossRef{
|
||||
{NormID: "EN-12312-3", BatchID: "4b", Notes: "Aircraft ground support — Part 3: conveyor belt vehicles.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12312-3:2017-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12312-4", BatchID: "4b", Notes: "Aircraft ground support — Part 4: passenger boarding bridges.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12312-4:2014-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-SAE", Identifier: "SAE ARP 1247", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-12312-5", BatchID: "4b", Notes: "Aircraft ground support — Part 5: aircraft fuelling equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12312-5:2018-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 407-2022 (Aircraft Fuel Servicing)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-12312-6", BatchID: "4b", Notes: "Aircraft ground support — Part 6: deicers and deicing/anti-icing equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12312-6:2017-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-SAE", Identifier: "SAE ARP 5660 (Deicing/Anti-Icing)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-12312-7", BatchID: "4b", Notes: "Aircraft ground support — Part 7: aircraft movement equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12312-7:2021-09", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12312-8", BatchID: "4b", Notes: "Aircraft ground support — Part 8: maintenance steps and platforms.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12312-8:2018-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12312-9", BatchID: "4b", Notes: "Aircraft ground support — Part 9: container/pallet loaders.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12312-9:2014-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12312-10", BatchID: "4b", Notes: "Aircraft ground support — Part 10: container/pallet transporters.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12312-10:2005-05", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12312-11", BatchID: "4b", Notes: "Aircraft ground support — Part 11: container/pallet dollies and loose load trailers.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12312-11:2014-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12312-12", BatchID: "4b", Notes: "Aircraft ground support — Part 12: potable water service equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12312-12:2017-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12312-13", BatchID: "4b", Notes: "Aircraft ground support — Part 13: lavatory service equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12312-13:2017-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12312-14", BatchID: "4b", Notes: "Aircraft ground support — Part 14: passenger boarding/disembarking vehicles.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12312-14:2014-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12312-15", BatchID: "4b", Notes: "Aircraft ground support — Part 15: baggage and equipment tractors.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12312-15:2018-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12312-16", BatchID: "4b", Notes: "Aircraft ground support — Part 16: air start equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12312-16:2005-05", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12312-17", BatchID: "4b", Notes: "Aircraft ground support — Part 17: air conditioning equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12312-17:2005-05", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12312-18", BatchID: "4b", Notes: "Aircraft ground support — Part 18: nitrogen or oxygen units.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12312-18:2017-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12312-19", BatchID: "4b", Notes: "Aircraft ground support — Part 19: aircraft jacks, axle jacks and hydraulic tail stanchions.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12312-19:2014-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12312-20", BatchID: "4b", Notes: "Aircraft ground support — Part 20: electrical ground power units.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12312-20:2017-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12385-1", BatchID: "4b", Notes: "Steel wire ropes — safety, Part 1: general requirements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12385-1:2009-01", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 17893 (Steel wire ropes — Vocabulary)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-ASME", Identifier: "ASME B30.30-2019 (Ropes)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-12385-2", BatchID: "4b", Notes: "Steel wire ropes — Part 2: definitions, designation, classification.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12385-2:2008-06", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12385-3", BatchID: "4b", Notes: "Steel wire ropes — Part 3: information for use and maintenance.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12385-3:2021-03", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12385-4", BatchID: "4b", Notes: "Steel wire ropes — Part 4: stranded ropes for general lifting applications.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12385-4:2008-06", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME B30.30", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-12385-5", BatchID: "4b", Notes: "Steel wire ropes — Part 5: stranded ropes for lifts.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12385-5:2021-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME A17.6-2017 (Suspension, Compensation, Governor Ropes)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-12385-10", BatchID: "4b", Notes: "Steel wire ropes — Part 10: spiral ropes for general structural applications.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12385-10:2008-06", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12415", BatchID: "4b", Notes: "Machine tools safety — small numerically controlled turning machines and turning centres.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12415:2002-04 (withdrawn, see EN ISO 23125)", Relation: "superseded_by", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 23125:2015", Relation: "supersedes", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12418", BatchID: "4b", Notes: "Masonry/stone-cutting saws for site work.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12418:2009-06", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1926.303 (Abrasive wheels and tools)", Relation: "partial", Confidence: "medium"},
|
||||
}},
|
||||
{NormID: "EN-12478", BatchID: "4b", Notes: "Industrial trucks — design specifications for fork carriages.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12478:2000-09", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12653", BatchID: "4b", Notes: "Industrial fans — safety, balance quality.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12653 (drafted; see ISO 14694)", Relation: "partial", Confidence: "medium"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 14694:2003 (Industrial Fans — Balance Quality)", Relation: "equivalent", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-12717", BatchID: "4b", Notes: "Machine tools safety — drilling machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12717:2009-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.8-2001 (R2017) (Manual Milling, Drilling, Boring)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-12750", BatchID: "4b", Notes: "Safety of woodworking machines — four-sided moulding machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12750:2013-09", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12779", BatchID: "4b", Notes: "Safety of woodworking machines — chip and dust extraction systems.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12779:2015-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 664 (Wood Processing/Woodworking)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-12810-1", BatchID: "4b", Notes: "Façade scaffolds made of prefabricated components — Part 1: products specifications.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12810-1:2004-03", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI A10.8-2019 (Scaffolding Safety)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1926.451", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-12810-2", BatchID: "4b", Notes: "Façade scaffolds — Part 2: particular methods of structural design.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12810-2:2004-03", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12811-1", BatchID: "4b", Notes: "Temporary works equipment — Part 1: scaffolds, performance requirements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12811-1:2004-03", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI A10.8-2019", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-12811-2", BatchID: "4b", Notes: "Temporary works equipment — Part 2: information on materials.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12811-2:2004-03", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12811-3", BatchID: "4b", Notes: "Temporary works — Part 3: load testing.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12811-3:2003-02", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12921-4", BatchID: "4b", Notes: "Surface cleaning machines — Part 4: safety for machines using halogenated solvents.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12921-4:2017-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-12929-1", BatchID: "4b", Notes: "Cableway installations — general requirements, Part 1: requirements for all installations.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12929-1:2015-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B77.1-2017 (Passenger Ropeways)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-12952-3", BatchID: "4b", Notes: "Water-tube boilers — Part 3: design and calculation for pressure parts.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12952-3:2020-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME BPVC Section I", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-12952-5", BatchID: "4b", Notes: "Water-tube boilers — Part 5: workmanship and construction of pressure parts.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12952-5:2021-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME BPVC Section I", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-12952-6", BatchID: "4b", Notes: "Water-tube boilers — Part 6: inspection during construction.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 12952-6:2021-09", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13001-1", BatchID: "4b", Notes: "Cranes — general design, Part 1: general principles and requirements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13001-1:2018-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME B30.2 + ASME BTH-1-2020 (Design of Below-the-Hook Lifting Devices)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-13001-2", BatchID: "4b", Notes: "Cranes — general design, Part 2: load actions.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13001-2:2021-09", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13001-3-1", BatchID: "4b", Notes: "Cranes — general design, Part 3-1: limit states / proof of competence of steel structures.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13001-3-1:2018-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13001-3-2", BatchID: "4b", Notes: "Cranes — Part 3-2: proof of competence of wire ropes in reeving systems.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13001-3-2:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13001-3-3", BatchID: "4b", Notes: "Cranes — Part 3-3: limit states / proof of competence of wheel-rail contacts.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13001-3-3:2018-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13001-3-4", BatchID: "4b", Notes: "Cranes — Part 3-4: machinery components.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13001-3-4:2018-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13001-3-5", BatchID: "4b", Notes: "Cranes — Part 3-5: forged and cast hooks.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13001-3-5:2016-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME B30.10-2019 (Hooks)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-13001-3-6", BatchID: "4b", Notes: "Cranes — Part 3-6: machinery components, hydraulic cylinders.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13001-3-6:2018-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package iace
|
||||
|
||||
// Cross-reference matrix — Batch 5a (next 50 alphabetical).
|
||||
// Covers glass machinery (EN 13035), ladders (EN 131), pressure vessels +
|
||||
// piping subparts, swimming-pool equipment (EN 13451), explosives (EN 13631),
|
||||
// fume cupboards (EN 14175), and amusement rides (EN 13814).
|
||||
|
||||
func init() {
|
||||
registerCrossRefs(batch5aCrossRefs())
|
||||
}
|
||||
|
||||
func batch5aCrossRefs() []NormCrossRef {
|
||||
return []NormCrossRef{
|
||||
{NormID: "EN-13023", BatchID: "5a", Notes: "Noise measurement method for printing, paper-converting, paper-making machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13023:2004-09", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13035-1", BatchID: "5a", Notes: "Machines for glass manufacture — storage, handling, transportation Part 1: storage outside.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13035-1:2008-02", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13035-2", BatchID: "5a", Notes: "Glass machinery — Part 2: storage inside.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13035-2:2008-02", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13035-3", BatchID: "5a", Notes: "Glass machinery — Part 3: cutting machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13035-3:2010-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13035-4", BatchID: "5a", Notes: "Glass machinery — Part 4: tilting tables.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13035-4:2003-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13035-5", BatchID: "5a", Notes: "Glass machinery — Part 5: machines and installations for stacking and de-stacking.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13035-5:2006-11", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13035-6", BatchID: "5a", Notes: "Glass machinery — Part 6: breakout machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13035-6:2006-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13035-7", BatchID: "5a", Notes: "Glass machinery — Part 7: cutting machines for laminated glass.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13035-7:2006-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13035-9", BatchID: "5a", Notes: "Glass machinery — Part 9: washing installations.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13035-9:2006-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13035-11", BatchID: "5a", Notes: "Glass machinery — Part 11: drilling machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13035-11:2006-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13053", BatchID: "5a", Notes: "Ventilation for buildings — air handling units, ratings and performance.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13053:2020-05", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-AHRI", Identifier: "AHRI Standard 410-2014 (Forced-Circulation Air-Cooling)", Relation: "partial", Confidence: "medium"},
|
||||
}},
|
||||
{NormID: "EN-131-1", BatchID: "5a", Notes: "Ladders — Part 1: terms, types, functional sizes.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 131-1:2020-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI A14.1/A14.2/A14.5 (Wood/Metal/Reinforced Plastic Ladders)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1926.1053", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-131-2", BatchID: "5a", Notes: "Ladders — Part 2: requirements, testing, marking.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 131-2:2017-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI A14 series", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-131-3", BatchID: "5a", Notes: "Ladders — Part 3: marking and user instructions.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 131-3:2018-06", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-131-4", BatchID: "5a", Notes: "Ladders — Part 4: single or multiple hinge-joint ladders.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 131-4:2020-05", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13208", BatchID: "5a", Notes: "Food processing machinery — vegetable peelers.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13208:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13241", BatchID: "5a", Notes: "Industrial, commercial and garage doors and gates — product standard.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13241:2021-06", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-UL", Identifier: "UL 325-2017", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-ASTM", Identifier: "ASTM F2200-22 (Gates)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-13256", BatchID: "5a", Notes: "Thermal insulation products for building equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13256:2017-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13288", BatchID: "5a", Notes: "Food processing machinery — lifting and tilting machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13288:2017-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13379", BatchID: "5a", Notes: "Food processing — pasta-processing machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13379:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13445-2", BatchID: "5a", Notes: "Unfired pressure vessels — Part 2: materials.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13445-2:2021-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME BPVC Section II (Materials)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-13445-4", BatchID: "5a", Notes: "Unfired pressure vessels — Part 4: fabrication.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13445-4:2021-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME BPVC Section VIII Div.1/2", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-13445-5", BatchID: "5a", Notes: "Unfired pressure vessels — Part 5: inspection and testing.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13445-5:2021-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME BPVC Section V (Non-Destructive Examination)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-13451-1", BatchID: "5a", Notes: "Swimming pool equipment — general safety requirements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13451-1:2018-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-APSP", Identifier: "ANSI/APSP/ICC-1 (Public Pools and Spas)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-13451-2", BatchID: "5a", Notes: "Swimming pool equipment — Part 2: ladders, stepladders, and handrails.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13451-2:2015-09", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13451-3", BatchID: "5a", Notes: "Swimming pool equipment — Part 3: inlets, outlets, water/air based water leisure features.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13451-3:2015-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-APSP", Identifier: "ANSI/APSP/ICC-7 (Suction Entrapment Avoidance)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-13451-4", BatchID: "5a", Notes: "Swimming pool equipment — Part 4: starting platforms.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13451-4:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13451-5", BatchID: "5a", Notes: "Swimming pool equipment — Part 5: lane lines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13451-5:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13451-10", BatchID: "5a", Notes: "Swimming pool equipment — Part 10: diving platforms, diving boards, jump boards.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13451-10:2015-09", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13451-11", BatchID: "5a", Notes: "Swimming pool equipment — Part 11: movable pool floors and dividing walls.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13451-11:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13480-2", BatchID: "5a", Notes: "Metallic industrial piping — Part 2: materials.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13480-2:2017-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME B31.3 (Process Piping)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-13480-4", BatchID: "5a", Notes: "Metallic industrial piping — Part 4: fabrication and installation.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13480-4:2017-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13480-5", BatchID: "5a", Notes: "Metallic industrial piping — Part 5: inspection and testing.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13480-5:2017-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13534", BatchID: "5a", Notes: "Food processing machinery — meat injecting machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13534:2007-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13631-1", BatchID: "5a", Notes: "Explosives for civil uses — high explosives, Part 1: requirements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13631-1:2005-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-DOT", Identifier: "49 CFR Part 173 (Hazardous Materials Regulations — Explosives)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-13631-2", BatchID: "5a", Notes: "Explosives — Part 2: determination of thermal stability.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13631-2:2002-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13631-3", BatchID: "5a", Notes: "Explosives — Part 3: determination of sensitiveness to friction.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13631-3:2005-02", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13631-4", BatchID: "5a", Notes: "Explosives — Part 4: determination of sensitiveness to impact.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13631-4:2002-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13731", BatchID: "5a", Notes: "Vibration isolating systems — performance requirements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13731:2007-09", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13779", BatchID: "5a", Notes: "Ventilation for non-residential buildings — performance requirements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13779:2007-09 (withdrawn, see EN 16798-3)", Relation: "superseded_by", Confidence: "verified"},
|
||||
{Region: "US-ASHRAE", Identifier: "ASHRAE 62.1-2022 (Ventilation for Acceptable Indoor Air Quality)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-13788", BatchID: "5a", Notes: "Machine tools safety — multi-spindle automatic turning machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13788:2002-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13814", BatchID: "5a", Notes: "Amusement rides and amusement devices — safety.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13814:2005-06", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASTM", Identifier: "ASTM F2291-22 (Design of Amusement Rides and Devices)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-ASTM", Identifier: "ASTM F1193-23 (Quality, Manufacture, Use)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-13852-2", BatchID: "5a", Notes: "Cranes — offshore cranes, floating cranes.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13852-2:2018-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13870", BatchID: "5a", Notes: "Food processing machinery — portion cutting machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13870:2015-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-13898", BatchID: "5a", Notes: "Machine tools safety — sawing machines for cold metal.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 13898:2018-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.10-2003 (R2018) (Metal Sawing Machines)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-14043", BatchID: "5a", Notes: "High-rise aerial appliances for fire services — turntable ladders with combined movements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14043:2014-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 1901-2024 §13 (Aerial Apparatus)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-14044", BatchID: "5a", Notes: "High-rise aerial appliances for fire services — turntable ladders with sequential movements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14044:2014-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-14070", BatchID: "5a", Notes: "Machine tools safety — transfer and special-purpose machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14070:2009-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-14175-1", BatchID: "5a", Notes: "Fume cupboards — Part 1: vocabulary.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14175-1:2003-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/AIHA Z9.5-2022 (Laboratory Ventilation)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-ASHRAE", Identifier: "ASHRAE 110-2016 (Method of Testing Performance of Laboratory Fume Hoods)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package iace
|
||||
|
||||
// Cross-reference matrix — Batch 5b (next 50 alphabetical).
|
||||
// Covers more fume cupboards (EN 14175), windows/doors (EN 14351), refuse
|
||||
// collection vehicles (EN 1501), drilling/foundation (EN 16228), respiratory
|
||||
// (EN 149), eye protection (EN 166), fire-service vehicles (EN 1846), and
|
||||
// the start of the circular saw sub-series (EN 1870-x).
|
||||
|
||||
func init() {
|
||||
registerCrossRefs(batch5bCrossRefs())
|
||||
}
|
||||
|
||||
func batch5bCrossRefs() []NormCrossRef {
|
||||
return []NormCrossRef{
|
||||
{NormID: "EN-14175-2", BatchID: "5b", Notes: "Fume cupboards — Part 2: safety and performance requirements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14175-2:2003-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASHRAE", Identifier: "ASHRAE 110-2016", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-14175-3", BatchID: "5b", Notes: "Fume cupboards — Part 3: type test methods.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14175-3:2019-02", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-14175-4", BatchID: "5b", Notes: "Fume cupboards — Part 4: on-site test methods.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14175-4:2005-02", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-14175-7", BatchID: "5b", Notes: "Fume cupboards — Part 7: fume cupboards for high heat loads.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14175-7:2012-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-14351-1", BatchID: "5b", Notes: "Windows and doors — product standard, performance characteristics, Part 1: windows and external pedestrian doorsets.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14351-1:2016-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-AAMA", Identifier: "AAMA/WDMA/CSA 101/I.S.2/A440 (NAFS)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-1459-2", BatchID: "5b", Notes: "Industrial trucks — variable-reach rough-terrain trucks, Part 2: rotating slewing trucks.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1459-2:2017-07", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ITSDF B56.6", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-14618", BatchID: "5b", Notes: "Agglomerated stones — terminology and classification.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14618:2009-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-14886", BatchID: "5b", Notes: "Plastics and rubber machines — band knife cutting machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14886:2008-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-149", BatchID: "5b", Notes: "Respiratory protective devices — filtering half masks to protect against particles.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 149:2009-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NIOSH", Identifier: "42 CFR Part 84 (NIOSH-approved N95/P100/R95)", Relation: "partial", Confidence: "high", Notes: "EN FFP2 ≈ N95, FFP3 ≈ N99; tests differ slightly (sodium chloride vs paraffin oil)."},
|
||||
}},
|
||||
{NormID: "EN-1493", BatchID: "5b", Notes: "Vehicle lifts — safety.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1493:2022-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ALI ALCTV-2017 (Automotive Lifts — Safety Requirements for Construction)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-14973", BatchID: "5b", Notes: "Conveyor belts for use in underground installations — electrical/flammability safety.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 14973:2015-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-MSHA", Identifier: "30 CFR §75.1108 (Approval of Conveyor Belts)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-1501-1", BatchID: "5b", Notes: "Refuse collection vehicles — Part 1: rear-loaded refuse collection vehicles.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1501-1:2021-11", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI Z245.1-2017 (Mobile Wastes and Recyclable Materials Collection)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-1501-2", BatchID: "5b", Notes: "Refuse collection vehicles — Part 2: side-loaded refuse collection vehicles.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1501-2:2022-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1501-3", BatchID: "5b", Notes: "Refuse collection vehicles — Part 3: front-loaded refuse collection vehicles.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1501-3:2022-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-15056", BatchID: "5b", Notes: "Cranes — requirements for fork-arm attachments for industrial trucks.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 15056:2007-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-15163", BatchID: "5b", Notes: "Machines and plants for the exploitation and processing of natural stone — diamond wire saws.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 15163:2008-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-15288-1", BatchID: "5b", Notes: "Swimming pools — Part 1: safety requirements for design.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 15288-1:2019-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-APSP", Identifier: "ANSI/APSP/ICC-1 (Public Pools and Spas)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-15947-1", BatchID: "5b", Notes: "Pyrotechnic articles — fireworks, category F2 and F3, Part 1: terminology.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 15947-1:2016-07", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-APA", Identifier: "APA 87-1A (Consumer Fireworks)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-15947-2", BatchID: "5b", Notes: "Fireworks — Part 2: classification.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 15947-2:2016-07", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-15947-3", BatchID: "5b", Notes: "Fireworks — Part 3: minimum labelling requirements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 15947-3:2016-07", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-15947-4", BatchID: "5b", Notes: "Fireworks — Part 4: test methods.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 15947-4:2016-07", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-15947-5", BatchID: "5b", Notes: "Fireworks — Part 5: requirements for construction and performance.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 15947-5:2022-09", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1612-2", BatchID: "5b", Notes: "Plastics and rubber machines — reaction moulding machines, Part 2: dosing units.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1612-2:1999-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-16228-1", BatchID: "5b", Notes: "Drilling and foundation equipment — safety, Part 1: common requirements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 16228-1:2014-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1926 Subpart P (Excavations)", Relation: "partial", Confidence: "medium"},
|
||||
}},
|
||||
{NormID: "EN-16228-2", BatchID: "5b", Notes: "Drilling and foundation equipment — Part 2: mobile drill rigs.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 16228-2:2014-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-16228-3", BatchID: "5b", Notes: "Drilling and foundation equipment — Part 3: horizontal directional drilling equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 16228-3:2014-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-16228-4", BatchID: "5b", Notes: "Drilling and foundation equipment — Part 4: foundation equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 16228-4:2014-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-16228-5", BatchID: "5b", Notes: "Drilling and foundation equipment — Part 5: diaphragm walling equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 16228-5:2014-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-16228-6", BatchID: "5b", Notes: "Drilling and foundation equipment — Part 6: jetting, grouting and injection equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 16228-6:2014-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-16228-7", BatchID: "5b", Notes: "Drilling and foundation equipment — Part 7: interchangeable auxiliary equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 16228-7:2014-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-16450", BatchID: "5b", Notes: "Ambient air — automated measuring systems for the measurement of the concentration of particulate matter.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 16450:2017-05", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-EPA", Identifier: "40 CFR Part 50 Appendix L (PM2.5 Reference Method)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-166", BatchID: "5b", Notes: "Personal eye protection — specifications.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 166:2002-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ISEA Z87.1-2020 (Eye and Face Protection)", Relation: "partial", Confidence: "high", Notes: "EN 166 'B' impact = ANSI Z87+ basic impact; high-velocity tests differ."},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.133", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 14866-2006", Relation: "equivalent", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-16602", BatchID: "5b", Notes: "Space product assurance — series (ECSS Q-ST).", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 16602 series (ECSS adoptions)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 14620 series", Relation: "partial", Confidence: "medium"},
|
||||
}},
|
||||
{NormID: "EN-1673", BatchID: "5b", Notes: "Food processing machinery — rotary rack ovens.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1673:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1674", BatchID: "5b", Notes: "Food processing machinery — dough sheeting machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1674:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-16798-3", BatchID: "5b", Notes: "Energy performance of buildings — ventilation, Part 3: ventilation for non-residential buildings (replaces EN 13779).", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 16798-3:2017-11", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASHRAE", Identifier: "ASHRAE 62.1-2022", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-1845", BatchID: "5b", Notes: "Footwear manufacturing machinery — moulding machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1845:2007-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1846-1", BatchID: "5b", Notes: "Fire-fighting vehicles and equipment — Part 1: nomenclature and designation.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1846-1:2011-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 1901-2024 (Automotive Fire Apparatus)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-1846-2", BatchID: "5b", Notes: "Fire-fighting vehicles — Part 2: common requirements, safety and performance.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1846-2:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 1901-2024", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-1846-3", BatchID: "5b", Notes: "Fire-fighting vehicles — Part 3: permanently-installed equipment, safety and performance.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1846-3:2021-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1870-2", BatchID: "5b", Notes: "Safety of woodworking machines — circular sawing machines, Part 2: horizontal beam panel saws.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1870-2:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1870-10", BatchID: "5b", Notes: "Woodworking machines — circular sawing — Part 10: automatic and semi-automatic up-cutting cross-cut saws.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1870-10:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1870-11", BatchID: "5b", Notes: "Woodworking machines — circular sawing — Part 11: semi-automatic and automatic horizontal cross-cut saws (radial arm saws).", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1870-11:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1870-12", BatchID: "5b", Notes: "Woodworking machines — circular sawing — Part 12: pendulum cross-cut saws.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1870-12:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1870-13", BatchID: "5b", Notes: "Woodworking machines — circular sawing — Part 13: horizontal beam panel saws with pressure beam.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1870-13:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1870-14", BatchID: "5b", Notes: "Woodworking machines — circular sawing — Part 14: vertical panel saws.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1870-14:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1870-15", BatchID: "5b", Notes: "Woodworking machines — circular sawing — Part 15: multi-blade cross-cut saws with integrated feed of workpiece.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1870-15:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1870-16", BatchID: "5b", Notes: "Woodworking machines — circular sawing — Part 16: double mitre cross-cut sawing machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1870-16:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1870-17", BatchID: "5b", Notes: "Woodworking machines — circular sawing — Part 17: hand-operated horizontal cross-cut single-blade saws.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1870-17:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1870-18", BatchID: "5b", Notes: "Woodworking machines — circular sawing — Part 18: dividing saws.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1870-18:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1870-19", BatchID: "5b", Notes: "Woodworking machines — circular sawing — Part 19: circular table saws (with and without sliding table) and building site saws.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1870-19:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package iace
|
||||
|
||||
// Cross-reference matrix — Batch 6a (next 50 alphabetical).
|
||||
// Covers remaining EN 1870 sawing parts, sterilizers (EN 285),
|
||||
// hearing/eye/glove PPE (EN 352, EN 388), refrigeration parts (EN 378),
|
||||
// road-building machines (EN 500), railway functional safety (EN 50126/8/9).
|
||||
|
||||
func init() {
|
||||
registerCrossRefs(batch6aCrossRefs())
|
||||
}
|
||||
|
||||
func batch6aCrossRefs() []NormCrossRef {
|
||||
return []NormCrossRef{
|
||||
{NormID: "EN-1870-3", BatchID: "6a", Notes: "Woodworking — circular saws, Part 3: down cutting cross-cut saws.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1870-3:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1870-4", BatchID: "6a", Notes: "Woodworking — circular saws, Part 4: multiblade rip saws.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1870-4:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1870-5", BatchID: "6a", Notes: "Woodworking — circular saws, Part 5: combined circular saw bench/up cutting cross-cut saws.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1870-5:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1870-6", BatchID: "6a", Notes: "Woodworking — circular saws, Part 6: circular saws for fire wood.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1870-6:2017-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1870-7", BatchID: "6a", Notes: "Woodworking — circular saws, Part 7: single blade log circular sawing machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1870-7:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1870-8", BatchID: "6a", Notes: "Woodworking — circular saws, Part 8: single blade edging circular sawing machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1870-8:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1870-9", BatchID: "6a", Notes: "Woodworking — circular saws, Part 9: double blade circular sawing machines for cross-cutting.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1870-9:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1886", BatchID: "6a", Notes: "Ventilation for buildings — air handling units, mechanical performance.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1886:2008-07", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-AHRI", Identifier: "AHRI 410-2014 + ASHRAE 41 series", Relation: "partial", Confidence: "medium"},
|
||||
}},
|
||||
{NormID: "EN-1889-1", BatchID: "6a", Notes: "Mining machines — mobile underground machinery, Part 1: rubber-tyred machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1889-1:2011-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-OSHA", Identifier: "30 CFR (Mine Safety and Health)", Relation: "partial", Confidence: "medium"},
|
||||
}},
|
||||
{NormID: "EN-1889-2", BatchID: "6a", Notes: "Mining machines — mobile underground machinery, Part 2: rail locomotives.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1889-2:2011-09", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-1970", BatchID: "6a", Notes: "Adjustable beds for disabled persons (legacy; superseded by EN 60601-2-52).", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 1970:2000-09 (withdrawn)", Relation: "superseded_by", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 60601-2-52 (Medical Beds)", Relation: "supersedes", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-203-1", BatchID: "6a", Notes: "Gas heated catering equipment — Part 1: general safety rules.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 203-1:2014-11", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI Z83.11 (Commercial Cooking Appliances)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-285", BatchID: "6a", Notes: "Sterilization — steam sterilizers — large sterilizers.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 285:2016-07", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/AAMI ST79-2017 (Comprehensive Guide to Steam Sterilization)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-30-1-1", BatchID: "6a", Notes: "Domestic cooking appliances burning gas — Part 1-1: safety, general.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 30-1-1:2008-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI Z21.1 (Household Cooking Gas Appliances)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-30-1-4", BatchID: "6a", Notes: "Domestic gas cookers — Part 1-4: appliances having one or more burners with automatic burner control.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 30-1-4:2003-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-352-1", BatchID: "6a", Notes: "Hearing protectors — general requirements, Part 1: ear-muffs.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 352-1:2021-01", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ASA S3.19-1974 (or S12.6-2016)", Relation: "partial", Confidence: "high", Notes: "US uses NRR (Noise Reduction Rating) computed differently than EN SNR/H/M/L."},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.95 + EPA 40 CFR 211 Subpart B", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB/T 23466-2009", Relation: "equivalent", Confidence: "medium"},
|
||||
}},
|
||||
{NormID: "EN-352-2", BatchID: "6a", Notes: "Hearing protectors — Part 2: ear-plugs.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 352-2:2021-01", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ASA S3.19-1974 / S12.6-2016", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-378-2", BatchID: "6a", Notes: "Refrigerating systems — Part 2: design, construction, testing, marking, documentation.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 378-2:2018-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASHRAE", Identifier: "ASHRAE 15-2022", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-378-3", BatchID: "6a", Notes: "Refrigerating systems — Part 3: installation site and personal protection.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 378-3:2018-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASHRAE", Identifier: "ASHRAE 15-2022", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-378-4", BatchID: "6a", Notes: "Refrigerating systems — Part 4: operation, maintenance, repair, recovery.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 378-4:2018-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-388", BatchID: "6a", Notes: "Protective gloves against mechanical risks.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 388:2019-03", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ISEA 105-2016 (Hand Protection Selection Criteria)", Relation: "partial", Confidence: "high", Notes: "EN scoring 0-4/5 vs ANSI A1-A9 cut levels; revised 2019 EN added TDM-100 method."},
|
||||
{Region: "CN-GB", Identifier: "GB/T 12624-2020", Relation: "equivalent", Confidence: "medium"},
|
||||
}},
|
||||
{NormID: "EN-45501", BatchID: "6a", Notes: "Metrological aspects of non-automatic weighing instruments.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 45501:2015-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "OIML R 76 (International Recommendation)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NIST", Identifier: "NIST Handbook 44 (Weights and Measures)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-474-12", BatchID: "6a", Notes: "Earth-moving machinery — Part 12: cable excavators.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 474-12:2022-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-474-13", BatchID: "6a", Notes: "Earth-moving machinery — Part 13: rollers.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 474-13:2022-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-500-1", BatchID: "6a", Notes: "Mobile road construction machinery — safety, Part 1: common requirements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 500-1:2009-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-500-2", BatchID: "6a", Notes: "Road construction — Part 2: road milling machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 500-2:2009-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-500-3", BatchID: "6a", Notes: "Road construction — Part 3: soil stabilisers and recycling machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 500-3:2009-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-500-4", BatchID: "6a", Notes: "Road construction — Part 4: compaction machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 500-4:2011-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-500-5", BatchID: "6a", Notes: "Road construction — Part 5: joint cutters.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 500-5:2009-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-500-6", BatchID: "6a", Notes: "Road construction — Part 6: pavers.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 500-6:2009-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-50126-1", BatchID: "6a", Notes: "Railway applications — RAMS (Reliability, Availability, Maintainability, Safety), Part 1: generic process.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 50126-1:2018-10 (VDE 0115-103-1)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 62278:2002", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-AAR", Identifier: "AAR M-1003 (Manual of Standards & Recommended Practices)", Relation: "partial", Confidence: "medium"},
|
||||
}},
|
||||
{NormID: "EN-50128", BatchID: "6a", Notes: "Railway applications — software for railway control and protection systems.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 50128:2014-09 (VDE 0831-128)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 62279:2015", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-50129", BatchID: "6a", Notes: "Railway applications — safety related electronic systems for signalling.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 50129:2019-06 (VDE 0831-129)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 62425:2007", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-525", BatchID: "6a", Notes: "Non-domestic direct gas-fired forced convection air heaters.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 525:2010-01", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI Z83.4 (Direct-Fired Gas Heaters)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-60335-2-67", BatchID: "6a", Notes: "Household and similar electrical appliances — particular requirements for floor treatment machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60335-2-67:2019-04 (VDE 0700-67)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 60335-2-67:2012", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-UL", Identifier: "UL 60335-2-67-2020", Relation: "equivalent", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-60335-2-68", BatchID: "6a", Notes: "Household appliances — particular requirements for spray extraction machines for commercial use.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60335-2-68:2013-12 (VDE 0700-68)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 60335-2-68:2012", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-60335-2-69", BatchID: "6a", Notes: "Household appliances — wet and dry vacuum cleaners, including power brush.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60335-2-69:2017-08 (VDE 0700-69)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 60335-2-69:2016", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-UL", Identifier: "UL 60335-2-69-2020", Relation: "equivalent", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-60335-2-72", BatchID: "6a", Notes: "Household appliances — automatic machines for floor treatment for commercial use.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60335-2-72:2013-04 (VDE 0700-72)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 60335-2-72:2012", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-60335-2-79", BatchID: "6a", Notes: "Household appliances — high-pressure cleaners and steam cleaners.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60335-2-79:2020-08 (VDE 0700-79)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 60335-2-79:2016", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-UL", Identifier: "UL 60335-2-79-2020", Relation: "equivalent", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-60974-2", BatchID: "6a", Notes: "Arc welding equipment — Part 2: liquid cooling systems.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60974-2:2019-05 (VDE 0544-2)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 60974-2:2019", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-60974-3", BatchID: "6a", Notes: "Arc welding equipment — Part 3: arc striking and stabilising devices.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60974-3:2019-05 (VDE 0544-3)", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-60974-4", BatchID: "6a", Notes: "Arc welding equipment — Part 4: periodic inspection and testing.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60974-4:2017-09 (VDE 0544-4)", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-60974-5", BatchID: "6a", Notes: "Arc welding equipment — Part 5: wire feeders.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60974-5:2019-05 (VDE 0544-5)", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-60974-6", BatchID: "6a", Notes: "Arc welding equipment — Part 6: limited-duty equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60974-6:2017-04 (VDE 0544-6)", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-60974-7", BatchID: "6a", Notes: "Arc welding equipment — Part 7: torches.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60974-7:2019-05 (VDE 0544-7)", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-60974-8", BatchID: "6a", Notes: "Arc welding equipment — Part 8: gas consoles for welding and plasma cutting systems.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60974-8:2010-03 (VDE 0544-8)", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-60974-9", BatchID: "6a", Notes: "Arc welding equipment — Part 9: installation and use.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60974-9:2018-08 (VDE 0544-9)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI Z49.1-2021 (Safety in Welding)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-60974-10", BatchID: "6a", Notes: "Arc welding equipment — Part 10: EMC requirements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60974-10:2020-09 (VDE 0544-10)", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-60974-11", BatchID: "6a", Notes: "Arc welding equipment — Part 11: electrode holders.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60974-11:2010-12 (VDE 0544-11)", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-60974-12", BatchID: "6a", Notes: "Arc welding equipment — Part 12: coupling devices for welding cables.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60974-12:2012-09 (VDE 0544-12)", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-60974-13", BatchID: "6a", Notes: "Arc welding equipment — Part 13: welding clamp.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60974-13:2013-06 (VDE 0544-13)", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
package iace
|
||||
|
||||
// Cross-reference matrix — Batch 6b (next 50 alphabetical).
|
||||
// Covers EN 60974-14 welding, EN 81 lift sub-parts, more woodworking
|
||||
// (EN 848-2/3, EN 859, EN 860, EN 930, EN 931, EN 940, EN 972), gas cylinders
|
||||
// (EN ISO 10297), industrial laundry (EN ISO 10472), hand tools, and PPE.
|
||||
|
||||
func init() {
|
||||
registerCrossRefs(batch6bCrossRefs())
|
||||
}
|
||||
|
||||
func batch6bCrossRefs() []NormCrossRef {
|
||||
return []NormCrossRef{
|
||||
{NormID: "EN-60974-14", BatchID: "6b", Notes: "Arc welding equipment — Part 14: calibration, validation and consistency testing.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60974-14:2018-12 (VDE 0544-14)", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-81-21", BatchID: "6b", Notes: "Safety rules for the construction and installation of lifts — Part 21: new passenger and goods passenger lifts in existing buildings.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 81-21:2022-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME A17.1-2022 (general)", Relation: "partial", Confidence: "medium"},
|
||||
}},
|
||||
{NormID: "EN-81-22", BatchID: "6b", Notes: "Safety rules for lifts — Part 22: electric lifts with inclined path.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 81-22:2022-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME A17.1 §5 (Inclined Elevators)", Relation: "partial", Confidence: "medium"},
|
||||
}},
|
||||
{NormID: "EN-81-28", BatchID: "6b", Notes: "Safety rules for lifts — Part 28: remote alarm on passenger and goods passenger lifts.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 81-28:2022-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-81-58", BatchID: "6b", Notes: "Safety rules for lifts — Part 58: landing doors fire resistance test.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 81-58:2022-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-81-71", BatchID: "6b", Notes: "Safety rules for lifts — Part 71: vandal-resistant lifts.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 81-71:2022-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-81-72", BatchID: "6b", Notes: "Safety rules for lifts — Part 72: firefighters lifts.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 81-72:2020-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME A17.1 §2.27 (Firefighters Operation)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-81-73", BatchID: "6b", Notes: "Safety rules for lifts — Part 73: behaviour of lifts in the event of fire.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 81-73:2020-09", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-81-76", BatchID: "6b", Notes: "Safety rules for lifts — Part 76: evacuation of persons with disabilities using lifts.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 81-76:2011-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-81-77", BatchID: "6b", Notes: "Safety rules for lifts — Part 77: lifts subject to seismic conditions.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 81-77:2018-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME A17.1 §8.4 (Elevator Safety Requirements for Seismic Risk)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-81-80", BatchID: "6b", Notes: "Safety rules for lifts — Part 80: improvement of safety of existing passenger and goods passenger lifts (SNEL).", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 81-80:2020-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME A17.3 (Safety Code for Existing Elevators)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-81-82", BatchID: "6b", Notes: "Safety rules for lifts — Part 82: rules for the improvement of accessibility of existing lifts.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 81-82:2013-09", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-848-2", BatchID: "6b", Notes: "Woodworking — Part 2: single-spindle hand-fed routing machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 848-2:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-848-3", BatchID: "6b", Notes: "Woodworking — Part 3: numerically controlled (NC) boring/routing machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 848-3:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-859", BatchID: "6b", Notes: "Woodworking — hand-fed surface planing machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 859:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-860", BatchID: "6b", Notes: "Woodworking — single-side thickness planing machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 860:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-930", BatchID: "6b", Notes: "Footwear, leather goods manufacturing machinery — roughing, scouring machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 930:2008-09", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-931", BatchID: "6b", Notes: "Footwear manufacturing machinery — lasting machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 931:2007-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-940", BatchID: "6b", Notes: "Woodworking — combined woodworking machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 940:2014-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-972", BatchID: "6b", Notes: "Tannery machines — reciprocating roller machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 972:2010-09", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-10297", BatchID: "6b", Notes: "Gas cylinders — refillable transportable cylinder valves.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 10297:2014-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 10297:2014", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-DOT", Identifier: "49 CFR Part 178 (Specifications for Packagings)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-ISO-10472-1", BatchID: "6b", Notes: "Industrial laundry machinery — common requirements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 10472-1:2008-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 10472-1:1997", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-10472-2", BatchID: "6b", Notes: "Industrial laundry — washing machines and washer-extractors.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 10472-2:2008-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 10472-2:1997", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-10472-3", BatchID: "6b", Notes: "Industrial laundry — washing tunnel lines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 10472-3:2008-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-10472-4", BatchID: "6b", Notes: "Industrial laundry — air dryers.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 10472-4:2008-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-10472-5", BatchID: "6b", Notes: "Industrial laundry — flatwork ironers, feeders and folders.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 10472-5:2008-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-10472-6", BatchID: "6b", Notes: "Industrial laundry — ironing and fusing presses.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 10472-6:2008-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-11148-2", BatchID: "6b", Notes: "Hand-held non-electric power tools — Part 2: cutting-off and crimping tools.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11148-2:2011-11", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 11148-2:2011", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-11148-4", BatchID: "6b", Notes: "Hand-held non-electric power tools — Part 4: non-rotary percussive power tools.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11148-4:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-11148-5", BatchID: "6b", Notes: "Hand-held non-electric power tools — Part 5: rotary percussive drills.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11148-5:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-11148-7", BatchID: "6b", Notes: "Hand-held non-electric power tools — Part 7: grinders.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11148-7:2012-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-11148-8", BatchID: "6b", Notes: "Hand-held non-electric power tools — Part 8: sanders and polishers.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11148-8:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-11148-9", BatchID: "6b", Notes: "Hand-held non-electric power tools — Part 9: die grinders.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11148-9:2012-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-11148-11", BatchID: "6b", Notes: "Hand-held non-electric power tools — Part 11: nibblers and shears.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11148-11:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-11148-12", BatchID: "6b", Notes: "Hand-held non-electric power tools — Part 12: small reciprocating and oscillating saws.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11148-12:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-11607-1", BatchID: "6b", Notes: "Packaging for terminally sterilized medical devices — Part 1: requirements for materials, sterile barrier systems and packaging.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11607-1:2020-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 11607-1:2019", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-FDA", Identifier: "21 CFR 820 + ASTM F88/F1980", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-ISO-11607-2", BatchID: "6b", Notes: "Packaging for medical devices — Part 2: validation requirements for forming, sealing and assembly processes.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11607-2:2020-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 11607-2:2019", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-11612", BatchID: "6b", Notes: "Protective clothing — clothing to protect against heat and flame.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11612:2015-11", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NFPA", Identifier: "NFPA 2112-2018 (Flame-Resistant Garments for Protection of Industrial Personnel)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-ASTM", Identifier: "ASTM F1959 (Determining Arc Rating)", Relation: "partial", Confidence: "medium"},
|
||||
}},
|
||||
{NormID: "EN-ISO-11806-1", BatchID: "6b", Notes: "Agricultural and forestry machinery — safety requirements and testing for portable, hand-held, internal combustion engine driven brush cutters and grass trimmers.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11806-1:2012-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/OPEI B175.3 (Grass Trimmers/Brush Cutters)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-ISO-13688", BatchID: "6b", Notes: "Protective clothing — general requirements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 13688:2013-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 13688:2013", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ISEA 101 (Limited-Use and Disposable Coveralls)", Relation: "partial", Confidence: "medium"},
|
||||
}},
|
||||
{NormID: "EN-ISO-14159", BatchID: "6b", Notes: "Safety of machinery — hygiene requirements for the design of machinery.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 14159:2008-07", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-NSF", Identifier: "NSF/ANSI/3-A 14159-1 (Hygienic Equipment for Food Processing)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-ISO-14890", BatchID: "6b", Notes: "Conveyor belts — specification for rubber- or plastics-covered conveyor belts of textile construction.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 14890:2013-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 14890:2013", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-14922-1", BatchID: "6b", Notes: "Thermal spraying — quality requirements of thermally sprayed structures, Part 1: guide for selection.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 14922-1:2019-11", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-14922-2", BatchID: "6b", Notes: "Thermal spraying — Part 2: comprehensive quality requirements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 14922-2:2019-11", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-14922-3", BatchID: "6b", Notes: "Thermal spraying — Part 3: standard quality requirements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 14922-3:2019-11", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-14922-4", BatchID: "6b", Notes: "Thermal spraying — Part 4: elementary quality requirements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 14922-4:2019-11", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-16063-1", BatchID: "6b", Notes: "Methods for the calibration of vibration and shock transducers — Part 1: basic concepts.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 16063-1:1999-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 16063-1:1998", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-17665-1", BatchID: "6b", Notes: "Sterilization of health care products — moist heat — Part 1: requirements for the development, validation and routine control of a sterilization process for medical devices.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 17665-1:2006-11", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 17665-1:2006", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/AAMI ST79-2017", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-ISO-19085-10", BatchID: "6b", Notes: "Woodworking machines safety — Part 10: building site saws.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 19085-10:2019-07", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 19085-10:2018", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package iace
|
||||
|
||||
// Cross-reference matrix — Batch 7a (final batch, first 36 entries).
|
||||
// Covers remaining woodworking (EN ISO 19085), safety footwear (EN ISO 20345),
|
||||
// stationary fitness (EN ISO 20957), additive manufacturing terminology
|
||||
// (EN ISO 52900), lawnmowers (EN ISO 5395), uniaxial testing machines (EN ISO
|
||||
// 7500), and additional safety valves (EN ISO 4126 parts).
|
||||
|
||||
func init() {
|
||||
registerCrossRefs(batch7aCrossRefs())
|
||||
}
|
||||
|
||||
func batch7aCrossRefs() []NormCrossRef {
|
||||
return []NormCrossRef{
|
||||
{NormID: "EN-ISO-19085-2", BatchID: "7a", Notes: "Woodworking machines — Part 2: horizontal beam panel circular sawing machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 19085-2:2018-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 19085-2:2017", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-19085-3", BatchID: "7a", Notes: "Woodworking machines — Part 3: numerically controlled boring and routing machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 19085-3:2018-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 19085-3:2017", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-19085-4", BatchID: "7a", Notes: "Woodworking machines — Part 4: vertical panel circular sawing machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 19085-4:2018-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-19085-6", BatchID: "7a", Notes: "Woodworking machines — Part 6: single-spindle vertical moulding machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 19085-6:2019-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-19085-7", BatchID: "7a", Notes: "Woodworking machines — Part 7: surface planing machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 19085-7:2019-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-19085-8", BatchID: "7a", Notes: "Woodworking machines — Part 8: rebating, calibrating sanding machines and edge sanding machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 19085-8:2017-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-19085-9", BatchID: "7a", Notes: "Woodworking machines — Part 9: circular sawing machines (with sliding table).", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 19085-9:2021-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-19085-11", BatchID: "7a", Notes: "Woodworking machines — Part 11: combined machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 19085-11:2020-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-19085-12", BatchID: "7a", Notes: "Woodworking machines — Part 12: tenoning/profiling machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 19085-12:2021-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-20345", BatchID: "7a", Notes: "Personal protective equipment — safety footwear.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 20345:2022-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 20345:2021", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASTM", Identifier: "ASTM F2413-18 (Performance Requirements for Protective Footwear)", Relation: "partial", Confidence: "high", Notes: "EN S1/S2/S3 ≈ ASTM safety class but tests differ in impact (200J vs 75 ft-lb)."},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1910.136", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-ISO-20346", BatchID: "7a", Notes: "Personal protective equipment — protective footwear (lower-spec category).", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 20346:2014-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASTM", Identifier: "ASTM F2413-18", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-ISO-20957-1", BatchID: "7a", Notes: "Stationary training equipment — Part 1: general safety requirements and test methods.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 20957-1:2014-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASTM", Identifier: "ASTM F2276-19 (Strength and Conditioning Equipment)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-ISO-20957-4", BatchID: "7a", Notes: "Stationary training — Part 4: strength training benches.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 20957-4:2017-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-20957-5", BatchID: "7a", Notes: "Stationary training — Part 5: stationary exercise bicycles and upper body crank training equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 20957-5:2016-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-20957-6", BatchID: "7a", Notes: "Stationary training — Part 6: treadmills.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 20957-6:2020-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASTM", Identifier: "ASTM F2115-14 (Motorized Treadmills)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-ISO-20957-9", BatchID: "7a", Notes: "Stationary training — Part 9: elliptical trainers.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 20957-9:2017-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-20957-10", BatchID: "7a", Notes: "Stationary training — Part 10: exercise bicycles with a fixed wheel or without freewheel.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 20957-10:2017-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-21530", BatchID: "7a", Notes: "Dentistry — materials used for dental equipment surfaces — determination of resistance to chemical disinfectants.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 21530:2005-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-22434", BatchID: "7a", Notes: "Transportable gas cylinders — inspection and maintenance of cylinder valves.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 22434:2011-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-283", BatchID: "7a", Notes: "Textile conveyor belts — full thickness tensile strength, elongation at break and elongation at the reference load.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 283:2015-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 283:2015", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-340", BatchID: "7a", Notes: "Conveyor belts — laboratory scale flammability characteristics — requirements and test method.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 340:2013-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 340:2013", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-3691-2", BatchID: "7a", Notes: "Industrial trucks — safety, Part 2: self-propelled variable-reach trucks.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 3691-2:2017-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 3691-2:2016", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ITSDF B56.6-2016 (Rough Terrain Trucks)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-ISO-4126-2", BatchID: "7a", Notes: "Safety devices for protection against excessive pressure — Part 2: bursting disc safety devices.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4126-2:2019-02", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASME", Identifier: "ASME BPVC Section VIII Div.1 §UG-127", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-ISO-4126-3", BatchID: "7a", Notes: "Safety devices — Part 3: safety valves and bursting disc safety devices in combination.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4126-3:2019-02", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-4126-5", BatchID: "7a", Notes: "Safety devices — Part 5: controlled safety pressure relief systems (CSPRS).", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4126-5:2014-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-4126-6", BatchID: "7a", Notes: "Safety devices — Part 6: application, selection and installation of bursting disc safety devices.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4126-6:2019-02", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-4126-7", BatchID: "7a", Notes: "Safety devices — Part 7: common data.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4126-7:2014-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-52900", BatchID: "7a", Notes: "Additive manufacturing — general principles — terminology.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO/ASTM 52900:2022-03", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO/ASTM 52900:2021", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASTM", Identifier: "ASTM F52900-21", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-52901", BatchID: "7a", Notes: "Additive manufacturing — requirements for purchased AM parts.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO/ASTM 52901:2017-07", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASTM", Identifier: "ASTM F52901-17", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-52910", BatchID: "7a", Notes: "Additive manufacturing — design — requirements, guidelines, and recommendations.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO/ASTM 52910:2019-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASTM", Identifier: "ASTM F52910-18", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-5344", BatchID: "7a", Notes: "Mechanical vibration — electrodynamic vibration generating systems — performance characteristics.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 5344:2018-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-5395-1", BatchID: "7a", Notes: "Garden equipment — safety requirements for internal combustion engine-powered lawnmowers — Part 1: terminology and common tests.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 5395-1:2014-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 5395-1:2013", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/OPEI B71.1-2017 (Consumer Turf Care Equipment)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "US-CPSC", Identifier: "16 CFR 1205 (Walk-Behind Power Mowers)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-ISO-5395-3", BatchID: "7a", Notes: "Lawnmowers — Part 3: ride-on lawnmowers with seated operator.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 5395-3:2014-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/OPEI B71.4 (Commercial Turf Care Equipment)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-ISO-7494-1", BatchID: "7a", Notes: "Dentistry — dental units — Part 1: general requirements and test methods.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 7494-1:2019-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-FDA", Identifier: "21 CFR 872 (Dental Devices)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-ISO-7494-2", BatchID: "7a", Notes: "Dentistry — Part 2: water and air supply.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 7494-2:2016-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "EN-ISO-7500-1", BatchID: "7a", Notes: "Metallic materials — calibration and verification of static uniaxial testing machines — Part 1: tension/compression testing machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 7500-1:2018-06", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ASTM", Identifier: "ASTM E4-21 (Force Verification of Testing Machines)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "EN-ISO-7500-2", BatchID: "7a", Notes: "Uniaxial testing machines — Part 2: tensile creep testing machines.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 7500-2:2006-07", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package iace
|
||||
|
||||
// Cross-reference matrix — Batch 7b (final batch, last 35 entries).
|
||||
// Covers medical electrical equipment (IEC 60601 family — major US adoption
|
||||
// ANSI/AAMI ES60601), chainsaws (ISO 11681), machine tools sawing (ISO 16093),
|
||||
// acoustics determination methods (ISO 3743/3745/3747), and remaining
|
||||
// agricultural machinery (ISO 4254 parts).
|
||||
|
||||
func init() {
|
||||
registerCrossRefs(batch7bCrossRefs())
|
||||
}
|
||||
|
||||
func batch7bCrossRefs() []NormCrossRef {
|
||||
return []NormCrossRef{
|
||||
{NormID: "IEC-60601-1", BatchID: "7b", Notes: "Medical electrical equipment — general requirements for basic safety and essential performance.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60601-1:2013-12 (VDE 0750-1)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 60601-1:2005+AMD1:2012+AMD2:2020", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/AAMI ES60601-1:2005/(R)2012+A1+A2", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-UL", Identifier: "UL 60601-1 (legacy edition)", Relation: "superseded_by", Confidence: "verified"},
|
||||
{Region: "CN-GB", Identifier: "GB 9706.1-2020", Relation: "equivalent", Confidence: "high"},
|
||||
{Region: "JP-JIS", Identifier: "JIS T 0601-1:2017", Relation: "equivalent", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "IEC-60601-1-2", BatchID: "7b", Notes: "Medical electrical equipment — EMC requirements.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60601-1-2:2016-05 (VDE 0750-1-2)", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 60601-1-2:2014+AMD1:2020", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/AAMI/IEC 60601-1-2:2014+A1:2021", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "IEC-60601-1-6", BatchID: "7b", Notes: "Medical electrical equipment — usability.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60601-1-6:2015-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/AAMI HE75:2009/(R)2018 (Human Factors Engineering)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "IEC-60601-1-8", BatchID: "7b", Notes: "Medical electrical equipment — alarm systems.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60601-1-8:2007-10", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 60601-1-8:2006+AMD1:2012+AMD2:2020", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "IEC-60601-1-9", BatchID: "7b", Notes: "Medical electrical equipment — environmentally conscious design.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60601-1-9:2014-06", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "IEC-60601-1-10", BatchID: "7b", Notes: "Medical electrical equipment — physiologic closed-loop controllers.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60601-1-10:2014-06", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "IEC-60601-1-11", BatchID: "7b", Notes: "Medical electrical equipment — home healthcare environment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60601-1-11:2015-08", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "IEC 60601-1-11:2015", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "IEC-60601-2-2", BatchID: "7b", Notes: "Medical equipment — particular requirements for HF surgical equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60601-2-2:2018-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/AAMI HF18-2009", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "IEC-60601-2-4", BatchID: "7b", Notes: "Medical equipment — particular requirements for cardiac defibrillators.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60601-2-4:2019-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "IEC-60601-2-10", BatchID: "7b", Notes: "Medical equipment — nerve and muscle stimulators.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60601-2-10:2017-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "IEC-60601-2-16", BatchID: "7b", Notes: "Medical equipment — haemodialysis equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60601-2-16:2019-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/AAMI RD52 (Hemodialysis Systems)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "IEC-60601-2-22", BatchID: "7b", Notes: "Medical equipment — laser equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60601-2-22:2013-10", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI Z136.3 (Lasers in Health Care)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "IEC-60601-2-25", BatchID: "7b", Notes: "Medical equipment — electrocardiographs.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60601-2-25:2015-11", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "IEC-60601-2-27", BatchID: "7b", Notes: "Medical equipment — electrocardiographic monitoring equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60601-2-27:2015-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/AAMI EC13 (Cardiac Monitors, Heart Rate Meters, and Alarms)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "IEC-60601-2-34", BatchID: "7b", Notes: "Medical equipment — invasive blood-pressure monitoring equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60601-2-34:2014-11", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "IEC-60601-2-37", BatchID: "7b", Notes: "Medical equipment — ultrasonic medical diagnostic and monitoring equipment.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60601-2-37:2016-08", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "IEC-60601-2-44", BatchID: "7b", Notes: "Medical equipment — X-ray equipment for computed tomography.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60601-2-44:2017-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-FDA", Identifier: "21 CFR 1020.33 (Computed Tomography Equipment)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "IEC-60601-2-46", BatchID: "7b", Notes: "Medical equipment — operating tables.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60601-2-46:2017-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "IEC-60601-2-52", BatchID: "7b", Notes: "Medical equipment — medical beds.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN 60601-2-52:2015-09", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "ISO-11681-1", BatchID: "7b", Notes: "Forestry machinery — portable chain-saw safety, Part 1: chain-saws for forest service.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11681-1:2011-11", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 11681-1:2011", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/OPEI B175.1-2012 (Chain Saws)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "ISO-11681-2", BatchID: "7b", Notes: "Forestry machinery — portable chain-saws, Part 2: chain-saws for tree service.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 11681-2:2011-11", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/OPEI B175.1-2012", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "ISO-16093", BatchID: "7b", Notes: "Machine tools safety — sawing machines for cold metal.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 16093:2018-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 16093:2017", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI B11.10-2003 (R2018) (Metal Sawing Machines)", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "ISO-3743-1", BatchID: "7b", Notes: "Acoustics — sound power levels — engineering methods for small, movable sources in reverberant fields.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 3743-1:2011-04", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 3743-1:2010", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI S12.51-2002 (R2017)", Relation: "equivalent", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "ISO-3743-2", BatchID: "7b", Notes: "Acoustics — sound power, Part 2: methods for special reverberation test rooms.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 3743-2:2018-12", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI S12.53-1999 (R2019)", Relation: "equivalent", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "ISO-3745", BatchID: "7b", Notes: "Acoustics — sound power levels — precision methods for anechoic and hemi-anechoic rooms.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 3745:2017-06", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 3745:2012+A1:2017", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI S12.55-2012", Relation: "equivalent", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "ISO-3747", BatchID: "7b", Notes: "Acoustics — sound power levels — survey method using reference sound source.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 3747:2011-03", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "INTL-ISO", Identifier: "ISO 3747:2010", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "ISO-4254-2", BatchID: "7b", Notes: "Agricultural machinery — safety, Part 2: anhydrous ammonia applicators.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4254-2:2010-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-ANSI", Identifier: "ANSI/ASABE S390.5", Relation: "partial", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "ISO-4254-3", BatchID: "7b", Notes: "Agricultural machinery — safety, Part 3: tractors.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4254-3:2010-09", Relation: "identical", Confidence: "verified"},
|
||||
{Region: "US-OSHA", Identifier: "29 CFR 1928.51 (Roll-Over Protective Structures)", Relation: "partial", Confidence: "high"},
|
||||
{Region: "CN-GB", Identifier: "GB 10395.3-2006", Relation: "equivalent", Confidence: "high"},
|
||||
}},
|
||||
{NormID: "ISO-4254-4", BatchID: "7b", Notes: "Agricultural machinery — Part 4: forage handling.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4254-4:2010-09", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "ISO-4254-8", BatchID: "7b", Notes: "Agricultural machinery — Part 8: solid fertilizer distributors.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4254-8:2018-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "ISO-4254-9", BatchID: "7b", Notes: "Agricultural machinery — Part 9: seed drills.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4254-9:2018-12", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "ISO-4254-10", BatchID: "7b", Notes: "Agricultural machinery — Part 10: rotary tedders and rakes.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4254-10:2010-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "ISO-4254-11", BatchID: "7b", Notes: "Agricultural machinery — Part 11: pick-up balers.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4254-11:2010-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
{NormID: "ISO-4254-13", BatchID: "7b", Notes: "Agricultural machinery — Part 13: large rotary mowers.", Mappings: []NormMapping{
|
||||
{Region: "EU-DIN", Identifier: "DIN EN ISO 4254-13:2012-04", Relation: "identical", Confidence: "verified"},
|
||||
}},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package iace
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRenderCrossRefAppendix_EmptyInput(t *testing.T) {
|
||||
got := RenderCrossRefAppendix(nil)
|
||||
if got != "" {
|
||||
t.Errorf("expected empty string for nil input, got %d bytes", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderCrossRefAppendix_UnknownIDs(t *testing.T) {
|
||||
got := RenderCrossRefAppendix([]string{"ISO-DOES-NOT-EXIST", "EN-ALSO-MISSING"})
|
||||
if got != "" {
|
||||
t.Errorf("expected empty string when no IDs match, got:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderCrossRefAppendix_ISO12100_RendersAllRegions(t *testing.T) {
|
||||
got := RenderCrossRefAppendix([]string{"ISO-12100"})
|
||||
if got == "" {
|
||||
t.Fatal("expected non-empty appendix for ISO-12100")
|
||||
}
|
||||
for _, want := range []string{
|
||||
"## Anhang: Internationale Aequivalenzen",
|
||||
"ISO-12100",
|
||||
"EU-DIN",
|
||||
"US-ANSI",
|
||||
"CN-GB",
|
||||
"JP-JIS",
|
||||
"DIN EN ISO 12100",
|
||||
"GB/T 15706",
|
||||
} {
|
||||
if !strings.Contains(got, want) {
|
||||
t.Errorf("expected appendix to contain %q, got:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderCrossRefAppendix_RegionOrdering(t *testing.T) {
|
||||
got := RenderCrossRefAppendix([]string{"EN-60204-1"})
|
||||
if got == "" {
|
||||
t.Fatal("expected non-empty appendix for EN-60204-1")
|
||||
}
|
||||
// EU-DIN must appear before US-NFPA which must appear before CN-GB.
|
||||
euIdx := strings.Index(got, "EU-DIN")
|
||||
usIdx := strings.Index(got, "US-NFPA")
|
||||
cnIdx := strings.Index(got, "CN-GB")
|
||||
if euIdx < 0 || usIdx < 0 || cnIdx < 0 {
|
||||
t.Fatalf("missing one of EU-DIN/US-NFPA/CN-GB markers, got:\n%s", got)
|
||||
}
|
||||
if !(euIdx < usIdx && usIdx < cnIdx) {
|
||||
t.Errorf("expected region order EU-DIN < US-NFPA < CN-GB, got positions %d, %d, %d", euIdx, usIdx, cnIdx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderCrossRefAppendix_MultipleNorms_SortedByID(t *testing.T) {
|
||||
got := RenderCrossRefAppendix([]string{"ISO-13850", "ISO-12100", "EN-60204-1"})
|
||||
if got == "" {
|
||||
t.Fatal("expected non-empty appendix")
|
||||
}
|
||||
// Expect EN-60204-1 first (alphabetical), then ISO-12100, then ISO-13850.
|
||||
en := strings.Index(got, "EN-60204-1")
|
||||
iso12100 := strings.Index(got, "ISO-12100")
|
||||
iso13850 := strings.Index(got, "ISO-13850")
|
||||
if en < 0 || iso12100 < 0 || iso13850 < 0 {
|
||||
t.Fatalf("missing one of the IDs in output:\n%s", got)
|
||||
}
|
||||
if !(en < iso12100 && iso12100 < iso13850) {
|
||||
t.Errorf("expected source-norm ordering by alphabetical ID, got positions %d, %d, %d", en, iso12100, iso13850)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderCrossRefAppendix_PipeEscape(t *testing.T) {
|
||||
got := RenderCrossRefAppendix([]string{"ISO-12100"})
|
||||
// Find a line that came from a mapping with the pipe character — none of
|
||||
// our identifiers contain literal '|' so this just checks that the table
|
||||
// header is intact (no accidental pipe injection).
|
||||
if !strings.Contains(got, "| EU Norm (verwendet) |") {
|
||||
t.Errorf("table header malformed:\n%s", got)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user