Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bf9d8a5ed3 | |||
| d45e08e25f | |||
| 3dbf3aa34a | |||
| 77308b783f | |||
| 9797234ff6 | |||
| 7080eb5f45 | |||
| c93cf2719a | |||
| 7a27dbc01b | |||
| de35dfce18 | |||
| 69240faf24 | |||
| f34305c0a1 | |||
| 2b5376ed54 | |||
| 958c03ab40 | |||
| fca67c1f43 | |||
| 70af018da5 | |||
| 0182c91ef9 | |||
| a67cfa7c4a | |||
| 3b7ab4cbd7 | |||
| 3469105d18 | |||
| 1414c63515 | |||
| 9f87bc5a2c | |||
| f5f4de7359 | |||
| 38d15d4d29 | |||
| 003eafa75d | |||
| b82853a95b | |||
| c060ac222a | |||
| 659c0505f8 | |||
| 02c2325e1b | |||
| d72aa10691 | |||
| 3c05ff8ef6 | |||
| 935c9205b9 | |||
| 826ce2a1b8 | |||
| bd2d6976d6 | |||
| a5d1814605 | |||
| ba07a7f6e6 | |||
| 708c61e50d | |||
| dc55253b9d | |||
| 8069d0ea89 | |||
| 4e9043f26d | |||
| 29fbd03c79 | |||
| 98e5b1a8aa | |||
| b175212516 | |||
| 16190583d1 | |||
| 70c9bfc069 | |||
| 4b9317b4fd | |||
| e4431da8d2 | |||
| 65f978368d |
@@ -202,9 +202,9 @@ export function ComplianceCheckTab() {
|
|||||||
setActiveCheckId(check_id)
|
setActiveCheckId(check_id)
|
||||||
localStorage.setItem(STORAGE_KEY_CHECK_ID, check_id)
|
localStorage.setItem(STORAGE_KEY_CHECK_ID, check_id)
|
||||||
|
|
||||||
// Poll for results (max 15 min = 300 polls x 3s)
|
// Poll for results (max 25 min = 500 polls x 3s)
|
||||||
let attempts = 0
|
let attempts = 0
|
||||||
while (attempts < 300) {
|
while (attempts < 500) {
|
||||||
await new Promise(r => setTimeout(r, 3000))
|
await new Promise(r => setTimeout(r, 3000))
|
||||||
const pollRes = await fetch(`/api/sdk/v1/agent/compliance-check?check_id=${check_id}`)
|
const pollRes = await fetch(`/api/sdk/v1/agent/compliance-check?check_id=${check_id}`)
|
||||||
if (!pollRes.ok) { attempts++; continue }
|
if (!pollRes.ok) { attempts++; continue }
|
||||||
@@ -235,7 +235,7 @@ export function ComplianceCheckTab() {
|
|||||||
}
|
}
|
||||||
attempts++
|
attempts++
|
||||||
}
|
}
|
||||||
if (attempts >= 300) {
|
if (attempts >= 500) {
|
||||||
localStorage.removeItem(STORAGE_KEY_CHECK_ID); setActiveCheckId('')
|
localStorage.removeItem(STORAGE_KEY_CHECK_ID); setActiveCheckId('')
|
||||||
throw new Error('Zeitlimit ueberschritten (15 Min)')
|
throw new Error('Zeitlimit ueberschritten (15 Min)')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ export interface BannerSite {
|
|||||||
site_name: string
|
site_name: string
|
||||||
site_url: string
|
site_url: string
|
||||||
is_active: boolean
|
is_active: boolean
|
||||||
|
tcf_enabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCookieBanner() {
|
export function useCookieBanner() {
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ export default function CookieBannerPage() {
|
|||||||
|
|
||||||
{/* Tab: TCF/IAB */}
|
{/* Tab: TCF/IAB */}
|
||||||
{activeTab === 'tcf' && (
|
{activeTab === 'tcf' && (
|
||||||
<TCFSettings siteId={activeSiteId || undefined} tcfEnabled={false}
|
<TCFSettings siteId={activeSiteId || undefined} tcfEnabled={sites.find(s => s.site_id === activeSiteId)?.tcf_enabled ?? false}
|
||||||
onToggle={(enabled) => {
|
onToggle={(enabled) => {
|
||||||
if (activeSiteId) {
|
if (activeSiteId) {
|
||||||
fetch(`/api/sdk/v1/banner/admin/sites/${activeSiteId}`, {
|
fetch(`/api/sdk/v1/banner/admin/sites/${activeSiteId}`, {
|
||||||
|
|||||||
@@ -101,7 +101,35 @@ function DocumentGeneratorPageInner() {
|
|||||||
}
|
}
|
||||||
}, [state?.complianceScope?.determinedLevel, state?.companyProfile])
|
}, [state?.complianceScope?.determinedLevel, state?.companyProfile])
|
||||||
|
|
||||||
// ── MODULE WIRING: CookieBanner → CONSENT + FEATURES ─────────────────────
|
// ── MODULE WIRING: Backend Banner-Config → CONSENT + FEATURES ────────────
|
||||||
|
useEffect(() => {
|
||||||
|
// Fetch real vendor/category data from backend if SDK state has no banner
|
||||||
|
if (state?.cookieBanner) return // SDK state takes priority
|
||||||
|
fetch('/api/sdk/v1/banner/admin/sites', { headers: { 'x-tenant-id': '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' } })
|
||||||
|
.then(r => r.json())
|
||||||
|
.then((sites: Array<{ site_id: string }>) => {
|
||||||
|
if (!sites?.length) return
|
||||||
|
return fetch(`/api/sdk/v1/banner/config/${sites[0].site_id}`, { headers: { 'x-tenant-id': '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' } })
|
||||||
|
})
|
||||||
|
.then(r => r?.json())
|
||||||
|
.then(config => {
|
||||||
|
if (!config?.vendors?.length) return
|
||||||
|
const analytics = config.vendors.filter((v: { category_key: string }) => v.category_key === 'statistics' || v.category_key === 'analytics').map((v: { vendor_name: string }) => v.vendor_name)
|
||||||
|
const marketing = config.vendors.filter((v: { category_key: string }) => v.category_key === 'marketing').map((v: { vendor_name: string }) => v.vendor_name)
|
||||||
|
setContext(prev => ({
|
||||||
|
...prev,
|
||||||
|
CONSENT: {
|
||||||
|
...prev.CONSENT,
|
||||||
|
ANALYTICS_TOOLS: analytics.length > 0 ? analytics.join(', ') : prev.CONSENT.ANALYTICS_TOOLS,
|
||||||
|
MARKETING_PARTNERS: marketing.length > 0 ? marketing.join(', ') : prev.CONSENT.MARKETING_PARTNERS,
|
||||||
|
},
|
||||||
|
FEATURES: { ...prev.FEATURES, CMP_NAME: 'BreakPilot CMP', CMP_LOGS_CONSENTS: true },
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}, [state?.cookieBanner])
|
||||||
|
|
||||||
|
// ── MODULE WIRING: CookieBanner SDK State → CONSENT + FEATURES ──────────
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const banner = state?.cookieBanner
|
const banner = state?.cookieBanner
|
||||||
if (!banner) return
|
if (!banner) return
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState } from 'react'
|
import { useState, useCallback } from 'react'
|
||||||
import { useBannerConsents } from '../_hooks/useBannerConsents'
|
import { useBannerConsents } from '../_hooks/useBannerConsents'
|
||||||
import { BannerConsentRecord, PAGE_SIZE } from '../_types'
|
import { BannerConsentRecord, PAGE_SIZE } from '../_types'
|
||||||
|
|
||||||
|
const BANNER_API = '/api/sdk/v1/banner'
|
||||||
|
const TENANT_ID = '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e'
|
||||||
|
|
||||||
function formatDate(iso: string | null): string {
|
function formatDate(iso: string | null): string {
|
||||||
if (!iso) return '—'
|
if (!iso) return '—'
|
||||||
return new Date(iso).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })
|
return new Date(iso).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })
|
||||||
@@ -42,12 +45,35 @@ const methodColors: Record<string, string> = {
|
|||||||
export default function BannerConsentsTab() {
|
export default function BannerConsentsTab() {
|
||||||
const {
|
const {
|
||||||
records, sites, selectedSite, changeSite,
|
records, sites, selectedSite, changeSite,
|
||||||
stats, currentPage, setCurrentPage, totalRecords, loading,
|
stats, currentPage, setCurrentPage, totalRecords, loading, reload,
|
||||||
} = useBannerConsents()
|
} = useBannerConsents()
|
||||||
|
|
||||||
const [detail, setDetail] = useState<BannerConsentRecord | null>(null)
|
const [detail, setDetail] = useState<BannerConsentRecord | null>(null)
|
||||||
|
const [linkEmailInput, setLinkEmailInput] = useState('')
|
||||||
|
const [linkingEmail, setLinkingEmail] = useState(false)
|
||||||
const totalPages = Math.ceil(totalRecords / PAGE_SIZE)
|
const totalPages = Math.ceil(totalRecords / PAGE_SIZE)
|
||||||
|
|
||||||
|
const withdrawConsent = useCallback(async (id: string) => {
|
||||||
|
if (!confirm('Consent wirklich widerrufen? Diese Aktion kann nicht rueckgaengig gemacht werden.')) return
|
||||||
|
await fetch(`${BANNER_API}/consent/${id}`, { method: 'DELETE', headers: { 'x-tenant-id': TENANT_ID } })
|
||||||
|
setDetail(null)
|
||||||
|
reload()
|
||||||
|
}, [reload])
|
||||||
|
|
||||||
|
const linkEmail = useCallback(async (record: BannerConsentRecord) => {
|
||||||
|
if (!linkEmailInput.includes('@')) return
|
||||||
|
setLinkingEmail(true)
|
||||||
|
await fetch(`${BANNER_API}/consent/link-email`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', 'x-tenant-id': TENANT_ID },
|
||||||
|
body: JSON.stringify({ site_id: record.site_id, device_fingerprint: record.device_fingerprint, email: linkEmailInput }),
|
||||||
|
})
|
||||||
|
setLinkingEmail(false)
|
||||||
|
setLinkEmailInput('')
|
||||||
|
setDetail({ ...record, linked_email: linkEmailInput })
|
||||||
|
reload()
|
||||||
|
}, [linkEmailInput, reload])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Stats + Site Selector */}
|
{/* Stats + Site Selector */}
|
||||||
@@ -184,6 +210,18 @@ export default function BannerConsentsTab() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{detail.vendor_consents && Object.keys(detail.vendor_consents).length > 0 && (
|
||||||
|
<div className="flex justify-between items-start">
|
||||||
|
<span className="text-gray-500">Vendors</span>
|
||||||
|
<div className="flex flex-wrap gap-1 justify-end">
|
||||||
|
{Object.entries(detail.vendor_consents).map(([name, accepted]) => (
|
||||||
|
<span key={name} className={`text-xs px-2 py-0.5 rounded-full ${accepted ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>
|
||||||
|
{name}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-gray-500">Methode</span>
|
<span className="text-gray-500">Methode</span>
|
||||||
<span>{detail.consent_method ? (
|
<span>{detail.consent_method ? (
|
||||||
@@ -192,9 +230,28 @@ export default function BannerConsentsTab() {
|
|||||||
</span>
|
</span>
|
||||||
) : '—'}</span>
|
) : '—'}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-gray-500">Verknüpft mit</span>
|
<span className="text-gray-500">Verknüpft mit</span>
|
||||||
<span>{detail.linked_email || '— (anonym)'}</span>
|
{detail.linked_email ? (
|
||||||
|
<span className="text-purple-600 text-xs">{detail.linked_email}</span>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
placeholder="E-Mail verknüpfen..."
|
||||||
|
value={linkEmailInput}
|
||||||
|
onChange={e => setLinkEmailInput(e.target.value)}
|
||||||
|
className="text-xs border border-gray-200 rounded px-2 py-1 w-40"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={() => linkEmail(detail)}
|
||||||
|
disabled={linkingEmail || !linkEmailInput.includes('@')}
|
||||||
|
className="text-xs px-2 py-1 bg-purple-600 text-white rounded disabled:opacity-40"
|
||||||
|
>
|
||||||
|
{linkingEmail ? '...' : 'Link'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between"><span className="text-gray-500">Erteilt</span><span>{formatDate(detail.created_at)}</span></div>
|
<div className="flex justify-between"><span className="text-gray-500">Erteilt</span><span>{formatDate(detail.created_at)}</span></div>
|
||||||
<div className="flex justify-between"><span className="text-gray-500">Ablauf</span><span>{formatDate(detail.expires_at)}</span></div>
|
<div className="flex justify-between"><span className="text-gray-500">Ablauf</span><span>{formatDate(detail.expires_at)}</span></div>
|
||||||
@@ -264,6 +321,16 @@ export default function BannerConsentsTab() {
|
|||||||
{detail.banner_config_hash && <div><span className="text-gray-500 text-xs">Config-Hash</span><p className="text-xs text-gray-600 font-mono">{detail.banner_config_hash}</p></div>}
|
{detail.banner_config_hash && <div><span className="text-gray-500 text-xs">Config-Hash</span><p className="text-xs text-gray-600 font-mono">{detail.banner_config_hash}</p></div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Widerruf-Button */}
|
||||||
|
<div className="border-t border-gray-100 pt-4 mt-4">
|
||||||
|
<button
|
||||||
|
onClick={() => withdrawConsent(detail.id)}
|
||||||
|
className="w-full px-4 py-2 text-xs font-semibold text-red-600 border border-red-200 rounded-lg hover:bg-red-50 transition-colors"
|
||||||
|
>
|
||||||
|
Consent widerrufen (Art. 17 DSGVO)
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ export interface BannerConsentRecord {
|
|||||||
device_fingerprint: string
|
device_fingerprint: string
|
||||||
categories: string[]
|
categories: string[]
|
||||||
vendors: string[]
|
vendors: string[]
|
||||||
|
vendor_consents: Record<string, boolean>
|
||||||
ip_hash: string | null
|
ip_hash: string | null
|
||||||
user_agent: string | null
|
user_agent: string | null
|
||||||
linked_email: string | null
|
linked_email: string | null
|
||||||
@@ -144,4 +145,5 @@ export interface BannerSite {
|
|||||||
site_id: string
|
site_id: string
|
||||||
site_name: string
|
site_name: string
|
||||||
site_url: string
|
site_url: string
|
||||||
|
tcf_enabled?: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-9
@@ -14,14 +14,21 @@ type TabType = 'matched' | 'missing' | 'extra'
|
|||||||
export function HazardComparisonTable({ matched, missing, extra }: Props) {
|
export function HazardComparisonTable({ matched, missing, extra }: Props) {
|
||||||
const [tab, setTab] = useState<TabType>('matched')
|
const [tab, setTab] = useState<TabType>('matched')
|
||||||
|
|
||||||
// Compute quality levels for matched pairs
|
// Split matches: >= 50% are real matches, < 50% are weak (shown separately)
|
||||||
const greenCount = matched.filter(p => p.match_score >= 0.7).length
|
const realMatched = matched.filter(p => p.match_score >= 0.5)
|
||||||
const yellowCount = matched.filter(p => p.match_score >= 0.4 && p.match_score < 0.7).length
|
const weakMatched = matched.filter(p => p.match_score < 0.5)
|
||||||
|
|
||||||
|
// Weak matches: GT entries go to "missing", engine entries go to "extra"
|
||||||
|
const allMissing = [...missing, ...weakMatched.map(w => w.gt_entry)]
|
||||||
|
const allExtra = [...extra, ...weakMatched.map(w => w.engine_hazard)]
|
||||||
|
|
||||||
|
const greenCount = realMatched.filter(p => p.match_score >= 0.7).length
|
||||||
|
const yellowCount = realMatched.filter(p => p.match_score >= 0.5 && p.match_score < 0.7).length
|
||||||
|
|
||||||
const tabs: { id: TabType; label: string; count: number; color: string }[] = [
|
const tabs: { id: TabType; label: string; count: number; color: string }[] = [
|
||||||
{ id: 'matched', label: `Zugeordnet (${greenCount} exakt, ${yellowCount} aehnlich)`, count: matched.length, color: 'text-green-600' },
|
{ id: 'matched', label: `Zugeordnet (${greenCount} exakt, ${yellowCount} aehnlich)`, count: realMatched.length, color: 'text-green-600' },
|
||||||
{ id: 'missing', label: 'Fehlend', count: missing.length, color: 'text-red-600' },
|
{ id: 'missing', label: 'Fehlend', count: allMissing.length, color: 'text-red-600' },
|
||||||
{ id: 'extra', label: 'Zusaetzlich', count: extra.length, color: 'text-gray-500' },
|
{ id: 'extra', label: 'Engine Findings', count: allExtra.length, color: 'text-blue-500' },
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -44,9 +51,9 @@ export function HazardComparisonTable({ matched, missing, extra }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
{tab === 'matched' && <MatchedTable pairs={matched} />}
|
{tab === 'matched' && <MatchedTable pairs={realMatched} />}
|
||||||
{tab === 'missing' && <MissingTable entries={missing} />}
|
{tab === 'missing' && <MissingTable entries={allMissing} />}
|
||||||
{tab === 'extra' && <ExtraTable entries={extra} />}
|
{tab === 'extra' && <ExtraTable entries={allExtra} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -114,6 +121,21 @@ function MatchedTable({ pairs }: { pairs: HazardMatchPair[] }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LIFECYCLE_LABELS: Record<string, string> = {
|
||||||
|
startup: 'Hochfahren', homing: 'Referenzfahrt', automatic_operation: 'Automatikbetrieb',
|
||||||
|
manual_operation: 'Handbetrieb', teach_mode: 'Einrichtbetrieb', maintenance: 'Wartung',
|
||||||
|
cleaning: 'Reinigung', emergency_stop: 'Not-Halt', recovery_mode: 'Wiederanlauf',
|
||||||
|
normal_operation: 'Automatikbetrieb', setup: 'Einrichten', changeover: 'Umruesten',
|
||||||
|
fault_clearing: 'Fehlersuche/Stoerungsbeseitigung', commissioning: 'Inbetriebnahme',
|
||||||
|
decommissioning: 'Demontage/Ausserbetriebnahme', transport: 'Transport',
|
||||||
|
assembly: 'Montage/Installation', inspection: 'Inspektion/Pruefung',
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatLifecycles(raw: string): string {
|
||||||
|
if (!raw) return '-'
|
||||||
|
return raw.split(',').map(s => s.trim()).map(s => LIFECYCLE_LABELS[s] || s).join(', ')
|
||||||
|
}
|
||||||
|
|
||||||
/** Side-by-side detail comparison of GT entry vs. Engine hazard */
|
/** Side-by-side detail comparison of GT entry vs. Engine hazard */
|
||||||
function DetailComparison({ gt, engine }: { gt: GroundTruthEntry; engine: HazardSummary }) {
|
function DetailComparison({ gt, engine }: { gt: GroundTruthEntry; engine: HazardSummary }) {
|
||||||
return (
|
return (
|
||||||
@@ -143,8 +165,14 @@ function DetailComparison({ gt, engine }: { gt: GroundTruthEntry; engine: Hazard
|
|||||||
<DetailRow label="Gefaehrdung" gt={engine.name} />
|
<DetailRow label="Gefaehrdung" gt={engine.name} />
|
||||||
<DetailRow label="Szenario" gt={engine.scenario || engine.description || '-'} />
|
<DetailRow label="Szenario" gt={engine.scenario || engine.description || '-'} />
|
||||||
<DetailRow label="Gefahrenstelle" gt={engine.zone || '-'} />
|
<DetailRow label="Gefahrenstelle" gt={engine.zone || '-'} />
|
||||||
|
{engine.lifecycle_phase && (
|
||||||
|
<DetailRow label="Lebensphasen" gt={formatLifecycles(engine.lifecycle_phase)} />
|
||||||
|
)}
|
||||||
<DetailRow label="Moeglicher Schaden" gt={engine.possible_harm || '-'} />
|
<DetailRow label="Moeglicher Schaden" gt={engine.possible_harm || '-'} />
|
||||||
<DetailRow label="Trigger" gt={engine.trigger_event || '-'} />
|
<DetailRow label="Trigger" gt={engine.trigger_event || '-'} />
|
||||||
|
{engine.affected_person && (
|
||||||
|
<DetailRow label="Betroffene Personen" gt={engine.affected_person} />
|
||||||
|
)}
|
||||||
{engine.mitigations && engine.mitigations.length > 0 ? (
|
{engine.mitigations && engine.mitigations.length > 0 ? (
|
||||||
<DetailRow label="Massnahmen" gt={engine.mitigations.join('\n')} multiline />
|
<DetailRow label="Massnahmen" gt={engine.mitigations.join('\n')} multiline />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export interface HazardSummary {
|
|||||||
component?: string; zone?: string; risk_level?: string
|
component?: string; zone?: string; risk_level?: string
|
||||||
description?: string; scenario?: string
|
description?: string; scenario?: string
|
||||||
possible_harm?: string; trigger_event?: string
|
possible_harm?: string; trigger_event?: string
|
||||||
|
affected_person?: string; lifecycle_phase?: string
|
||||||
mitigations?: string[]
|
mitigations?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ export default function BenchmarkPage() {
|
|||||||
const { result, gtLoaded, gtEntryCount, loading, error, importGT, runBenchmark } = useBenchmark(projectId)
|
const { result, gtLoaded, gtEntryCount, loading, error, importGT, runBenchmark } = useBenchmark(projectId)
|
||||||
const [gtProjectId, setGtProjectId] = useState('')
|
const [gtProjectId, setGtProjectId] = useState('')
|
||||||
|
|
||||||
const coveragePct = result ? Math.round(result.coverage_score * 100) : 0
|
// Only count matches >= 50% as real coverage
|
||||||
|
const realMatchCount = result ? (result.matched_pairs?.filter(m => m.match_score >= 0.5).length || 0) : 0
|
||||||
|
const coveragePct = result ? Math.round(realMatchCount * 100 / Math.max(result.total_gt, 1)) : 0
|
||||||
const measurePct = result ? Math.round(result.measure_coverage * 100) : 0
|
const measurePct = result ? Math.round(result.measure_coverage * 100) : 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -74,7 +76,7 @@ export default function BenchmarkPage() {
|
|||||||
<ScoreCard
|
<ScoreCard
|
||||||
label="Hazard Coverage"
|
label="Hazard Coverage"
|
||||||
value={`${coveragePct}%`}
|
value={`${coveragePct}%`}
|
||||||
sub={`${result.matched_pairs?.length || 0} / ${result.total_gt} erkannt`}
|
sub={`${realMatchCount} / ${result.total_gt} erkannt (>= 50% Match)`}
|
||||||
color={coveragePct >= 80 ? 'green' : coveragePct >= 50 ? 'yellow' : 'red'}
|
color={coveragePct >= 80 ? 'green' : coveragePct >= 50 ? 'yellow' : 'red'}
|
||||||
/>
|
/>
|
||||||
<ScoreCard
|
<ScoreCard
|
||||||
|
|||||||
@@ -0,0 +1,371 @@
|
|||||||
|
import { test, expect } from '@playwright/test'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CMP Phase 3 + DSR Integration Tests
|
||||||
|
*
|
||||||
|
* Tests the complete CMP lifecycle including:
|
||||||
|
* - Vendor-agnostic consent fields (consent_method, browser, os, etc.)
|
||||||
|
* - Script/cookie tracking (scripts_blocked, scripts_released, cookies_set)
|
||||||
|
* - Session ID tracking
|
||||||
|
* - GeoIP via timezone mapping
|
||||||
|
* - Vendor-level consent (vendor_consents dict)
|
||||||
|
* - DSR scenarios: Art. 15 Auskunft, Art. 17 Löschung, Art. 20 Portabilität
|
||||||
|
* - Email linking for DSR (device → user mapping)
|
||||||
|
* - Admin modal features (vendor display, withdraw, email linking)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const API_BASE = process.env.PLAYWRIGHT_API_URL || 'https://macmini:3007/api/sdk/v1/banner'
|
||||||
|
const TENANT_ID = '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e'
|
||||||
|
const HEADERS = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Tenant-ID': TENANT_ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
const TS = Date.now()
|
||||||
|
const SITE_ID = `e2e-cmp3-${TS}`
|
||||||
|
const DEVICE_FP = `e2e-device-${TS}`
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 1. Vendor-Agnostic Consent Fields
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
test.describe('Vendor-Agnostic Consent Fields', () => {
|
||||||
|
test('should store all 20+ fields on consent', async ({ request }) => {
|
||||||
|
// Create site config first
|
||||||
|
await request.post(`${API_BASE}/admin/sites`, {
|
||||||
|
headers: HEADERS,
|
||||||
|
data: { site_id: SITE_ID, site_name: 'E2E CMP Phase 3', site_url: 'https://test.example.com' },
|
||||||
|
})
|
||||||
|
|
||||||
|
// Record consent with all vendor-agnostic fields
|
||||||
|
const res = await request.post(`${API_BASE}/consent`, {
|
||||||
|
headers: HEADERS,
|
||||||
|
data: {
|
||||||
|
site_id: SITE_ID,
|
||||||
|
device_fingerprint: DEVICE_FP,
|
||||||
|
categories: ['essential', 'functional', 'analytics'],
|
||||||
|
vendors: ['Google Analytics', 'Matomo'],
|
||||||
|
vendor_consents: { 'Google Analytics': true, 'Matomo': true, 'Facebook Pixel': false },
|
||||||
|
user_agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) E2E-Test',
|
||||||
|
consent_method: 'custom_selection',
|
||||||
|
page_url: 'https://test.example.com/pricing',
|
||||||
|
referrer: 'https://google.com',
|
||||||
|
device_type: 'desktop',
|
||||||
|
browser: 'Chrome/120.0',
|
||||||
|
os: 'Mac OS X 10.15.7',
|
||||||
|
screen_resolution: '1920x1080',
|
||||||
|
consent_scope: 'domain',
|
||||||
|
session_id: 'e2e-session-001',
|
||||||
|
timezone: 'Europe/Berlin',
|
||||||
|
scripts_blocked: [{ src: 'https://connect.facebook.net/fbevents.js', category: 'marketing' }],
|
||||||
|
scripts_released: [{ src: 'https://www.googletagmanager.com/gtag/js', category: 'analytics' }],
|
||||||
|
cookies_set: [
|
||||||
|
{ name: '_ga', domain: '.test.example.com', expiry_days: 730, category: 'analytics' },
|
||||||
|
{ name: 'bp_consent', domain: '.test.example.com', expiry_days: 365, category: 'essential' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(res.status()).toBe(200)
|
||||||
|
const consent = await res.json()
|
||||||
|
expect(consent.id).toBeTruthy()
|
||||||
|
expect(consent.consent_method).toBe('custom_selection')
|
||||||
|
expect(consent.device_type).toBe('desktop')
|
||||||
|
expect(consent.browser).toBe('Chrome/120.0')
|
||||||
|
expect(consent.os).toBe('Mac OS X 10.15.7')
|
||||||
|
expect(consent.page_url).toBe('https://test.example.com/pricing')
|
||||||
|
expect(consent.session_id).toBe('e2e-session-001')
|
||||||
|
expect(consent.geo_country).toBe('DE') // Europe/Berlin → DE
|
||||||
|
expect(consent.scripts_released).toHaveLength(1)
|
||||||
|
expect(consent.cookies_set).toHaveLength(2)
|
||||||
|
expect(consent.vendor_consents).toEqual({ 'Google Analytics': true, 'Matomo': true, 'Facebook Pixel': false })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should update consent on same fingerprint (upsert)', async ({ request }) => {
|
||||||
|
const res = await request.post(`${API_BASE}/consent`, {
|
||||||
|
headers: HEADERS,
|
||||||
|
data: {
|
||||||
|
site_id: SITE_ID,
|
||||||
|
device_fingerprint: DEVICE_FP,
|
||||||
|
categories: ['essential'], // changed from all 3 to essential only
|
||||||
|
vendors: [],
|
||||||
|
consent_method: 'reject_all',
|
||||||
|
page_url: 'https://test.example.com/settings',
|
||||||
|
timezone: 'Europe/Vienna',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(res.status()).toBe(200)
|
||||||
|
const consent = await res.json()
|
||||||
|
expect(consent.consent_method).toBe('reject_all')
|
||||||
|
expect(consent.geo_country).toBe('AT') // Europe/Vienna → AT
|
||||||
|
expect(consent.categories).toEqual(['essential'])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 2. DSR Scenarios — Art. 15 Auskunft
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
test.describe('DSR — Art. 15 Auskunftsrecht', () => {
|
||||||
|
const DSR_EMAIL = `dsr-user-${TS}@example.com`
|
||||||
|
const DSR_DEVICE_1 = `dsr-desktop-${TS}`
|
||||||
|
const DSR_DEVICE_2 = `dsr-mobile-${TS}`
|
||||||
|
|
||||||
|
test.beforeAll(async ({ request }) => {
|
||||||
|
// Scenario: User visited website from 2 devices, then linked their email
|
||||||
|
|
||||||
|
// Device 1: Desktop consent
|
||||||
|
await request.post(`${API_BASE}/consent`, {
|
||||||
|
headers: HEADERS,
|
||||||
|
data: {
|
||||||
|
site_id: SITE_ID,
|
||||||
|
device_fingerprint: DSR_DEVICE_1,
|
||||||
|
categories: ['essential', 'analytics'],
|
||||||
|
consent_method: 'accept_all',
|
||||||
|
device_type: 'desktop',
|
||||||
|
browser: 'Firefox/121.0',
|
||||||
|
page_url: 'https://test.example.com/',
|
||||||
|
timezone: 'Europe/Berlin',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Device 2: Mobile consent
|
||||||
|
await request.post(`${API_BASE}/consent`, {
|
||||||
|
headers: HEADERS,
|
||||||
|
data: {
|
||||||
|
site_id: SITE_ID,
|
||||||
|
device_fingerprint: DSR_DEVICE_2,
|
||||||
|
categories: ['essential'],
|
||||||
|
consent_method: 'reject_all',
|
||||||
|
device_type: 'mobile',
|
||||||
|
browser: 'Safari/17.0',
|
||||||
|
page_url: 'https://test.example.com/pricing',
|
||||||
|
timezone: 'Europe/Berlin',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// User logs in and links email to both devices
|
||||||
|
await request.post(`${API_BASE}/consent/link-email`, {
|
||||||
|
headers: HEADERS,
|
||||||
|
data: { site_id: SITE_ID, device_fingerprint: DSR_DEVICE_1, email: DSR_EMAIL },
|
||||||
|
})
|
||||||
|
await request.post(`${API_BASE}/consent/link-email`, {
|
||||||
|
headers: HEADERS,
|
||||||
|
data: { site_id: SITE_ID, device_fingerprint: DSR_DEVICE_2, email: DSR_EMAIL },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Art. 15 — should find all consents by email', async ({ request }) => {
|
||||||
|
const res = await request.get(`${API_BASE}/consent/by-email/${DSR_EMAIL}`, { headers: HEADERS })
|
||||||
|
expect(res.status()).toBe(200)
|
||||||
|
const consents = await res.json()
|
||||||
|
expect(consents).toHaveLength(2)
|
||||||
|
expect(consents.map((c: { device_fingerprint: string }) => c.device_fingerprint).sort()).toEqual(
|
||||||
|
[DSR_DEVICE_1, DSR_DEVICE_2].sort()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Art. 15/20 — should export all consent data for DSR', async ({ request }) => {
|
||||||
|
const res = await request.get(`${API_BASE}/consent/dsr-export/${DSR_EMAIL}`, { headers: HEADERS })
|
||||||
|
expect(res.status()).toBe(200)
|
||||||
|
const exportData = await res.json()
|
||||||
|
expect(exportData.consents).toHaveLength(2)
|
||||||
|
expect(exportData.audit_trail.length).toBeGreaterThan(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Art. 17 — should delete all consents by email (erasure)', async ({ request }) => {
|
||||||
|
const res = await request.delete(`${API_BASE}/consent/by-email/${DSR_EMAIL}`, { headers: HEADERS })
|
||||||
|
expect(res.status()).toBe(200)
|
||||||
|
const result = await res.json()
|
||||||
|
expect(result.deleted_count).toBe(2)
|
||||||
|
|
||||||
|
// Verify deletion
|
||||||
|
const check = await request.get(`${API_BASE}/consent/by-email/${DSR_EMAIL}`, { headers: HEADERS })
|
||||||
|
const remaining = await check.json()
|
||||||
|
expect(remaining).toHaveLength(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 3. DSR Scenarios — Cookie Banner User (anonymous)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
test.describe('DSR — Anonymous Cookie Banner User', () => {
|
||||||
|
const ANON_DEVICE = `anon-user-${TS}`
|
||||||
|
|
||||||
|
test.beforeAll(async ({ request }) => {
|
||||||
|
await request.post(`${API_BASE}/consent`, {
|
||||||
|
headers: HEADERS,
|
||||||
|
data: {
|
||||||
|
site_id: SITE_ID,
|
||||||
|
device_fingerprint: ANON_DEVICE,
|
||||||
|
categories: ['essential', 'functional'],
|
||||||
|
consent_method: 'custom_selection',
|
||||||
|
device_type: 'tablet',
|
||||||
|
browser: 'Chrome/120.0',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should export consent by device fingerprint', async ({ request }) => {
|
||||||
|
const res = await request.get(
|
||||||
|
`${API_BASE}/consent/export?site_id=${SITE_ID}&device_fingerprint=${ANON_DEVICE}`,
|
||||||
|
{ headers: HEADERS }
|
||||||
|
)
|
||||||
|
expect(res.status()).toBe(200)
|
||||||
|
const data = await res.json()
|
||||||
|
expect(data.device_fingerprint).toBe(ANON_DEVICE)
|
||||||
|
expect(data.consents).toHaveLength(1)
|
||||||
|
expect(data.audit_trail.length).toBeGreaterThan(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should withdraw consent by ID', async ({ request }) => {
|
||||||
|
// Get consent ID first
|
||||||
|
const getRes = await request.get(
|
||||||
|
`${API_BASE}/consent?site_id=${SITE_ID}&device_fingerprint=${ANON_DEVICE}`,
|
||||||
|
{ headers: HEADERS }
|
||||||
|
)
|
||||||
|
const { consent } = await getRes.json()
|
||||||
|
expect(consent).toBeTruthy()
|
||||||
|
|
||||||
|
// Withdraw
|
||||||
|
const delRes = await request.delete(`${API_BASE}/consent/${consent.id}`, { headers: HEADERS })
|
||||||
|
expect(delRes.status()).toBe(200)
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
const checkRes = await request.get(
|
||||||
|
`${API_BASE}/consent?site_id=${SITE_ID}&device_fingerprint=${ANON_DEVICE}`,
|
||||||
|
{ headers: HEADERS }
|
||||||
|
)
|
||||||
|
const result = await checkRes.json()
|
||||||
|
expect(result.has_consent).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 4. DSR Scenarios — Login User (Customer) who also used Cookie Banner
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
test.describe('DSR — Customer with Banner + Login', () => {
|
||||||
|
const CUSTOMER_EMAIL = `customer-${TS}@company.com`
|
||||||
|
const CUSTOMER_DEVICE = `customer-device-${TS}`
|
||||||
|
|
||||||
|
test('full lifecycle: consent → login → link → Art.15 → Art.17', async ({ request }) => {
|
||||||
|
// Step 1: Anonymous visit → cookie consent
|
||||||
|
const consentRes = await request.post(`${API_BASE}/consent`, {
|
||||||
|
headers: HEADERS,
|
||||||
|
data: {
|
||||||
|
site_id: SITE_ID,
|
||||||
|
device_fingerprint: CUSTOMER_DEVICE,
|
||||||
|
categories: ['essential', 'analytics'],
|
||||||
|
consent_method: 'accept_all',
|
||||||
|
device_type: 'desktop',
|
||||||
|
browser: 'Edge/120.0',
|
||||||
|
page_url: 'https://test.example.com/',
|
||||||
|
timezone: 'Europe/Zurich',
|
||||||
|
scripts_released: [{ src: 'https://cdn.matomo.cloud/test.js', category: 'analytics' }],
|
||||||
|
cookies_set: [{ name: '_pk_id', domain: '.test.example.com', expiry_days: 393, category: 'analytics' }],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(consentRes.status()).toBe(200)
|
||||||
|
const consent = await consentRes.json()
|
||||||
|
expect(consent.geo_country).toBe('CH') // Europe/Zurich → CH
|
||||||
|
|
||||||
|
// Step 2: Customer logs in → email linked
|
||||||
|
const linkRes = await request.post(`${API_BASE}/consent/link-email`, {
|
||||||
|
headers: HEADERS,
|
||||||
|
data: { site_id: SITE_ID, device_fingerprint: CUSTOMER_DEVICE, email: CUSTOMER_EMAIL },
|
||||||
|
})
|
||||||
|
expect(linkRes.status()).toBe(200)
|
||||||
|
|
||||||
|
// Step 3: Art. 15 — Customer requests their data
|
||||||
|
const exportRes = await request.get(`${API_BASE}/consent/dsr-export/${CUSTOMER_EMAIL}`, { headers: HEADERS })
|
||||||
|
expect(exportRes.status()).toBe(200)
|
||||||
|
const exportData = await exportRes.json()
|
||||||
|
expect(exportData.consents.length).toBeGreaterThan(0)
|
||||||
|
expect(exportData.audit_trail.length).toBeGreaterThan(0)
|
||||||
|
|
||||||
|
// Verify export contains all consent details
|
||||||
|
const exported = exportData.consents[0]
|
||||||
|
expect(exported.categories).toContain('analytics')
|
||||||
|
expect(exported.linked_email).toBe(CUSTOMER_EMAIL)
|
||||||
|
|
||||||
|
// Step 4: Art. 17 — Customer requests erasure
|
||||||
|
const deleteRes = await request.delete(`${API_BASE}/consent/by-email/${CUSTOMER_EMAIL}`, { headers: HEADERS })
|
||||||
|
expect(deleteRes.status()).toBe(200)
|
||||||
|
|
||||||
|
// Step 5: Verify complete erasure
|
||||||
|
const verifyRes = await request.get(`${API_BASE}/consent/by-email/${CUSTOMER_EMAIL}`, { headers: HEADERS })
|
||||||
|
const remaining = await verifyRes.json()
|
||||||
|
expect(remaining).toHaveLength(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 5. Admin Dashboard Integration
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
test.describe('Admin Dashboard — Consent Management', () => {
|
||||||
|
const ADMIN_DEVICE = `admin-test-${TS}`
|
||||||
|
|
||||||
|
test.beforeAll(async ({ request }) => {
|
||||||
|
await request.post(`${API_BASE}/consent`, {
|
||||||
|
headers: HEADERS,
|
||||||
|
data: {
|
||||||
|
site_id: SITE_ID,
|
||||||
|
device_fingerprint: ADMIN_DEVICE,
|
||||||
|
categories: ['essential', 'functional', 'analytics'],
|
||||||
|
vendors: ['Matomo'],
|
||||||
|
vendor_consents: { Matomo: true },
|
||||||
|
consent_method: 'accept_all',
|
||||||
|
device_type: 'desktop',
|
||||||
|
browser: 'Chrome/121.0',
|
||||||
|
os: 'Windows NT 10.0',
|
||||||
|
screen_resolution: '2560x1440',
|
||||||
|
page_url: 'https://test.example.com/dashboard',
|
||||||
|
session_id: 'admin-session-001',
|
||||||
|
timezone: 'Europe/Berlin',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should list consents with new fields', async ({ request }) => {
|
||||||
|
const res = await request.get(`${API_BASE}/admin/consents?site_id=${SITE_ID}`, { headers: HEADERS })
|
||||||
|
expect(res.status()).toBe(200)
|
||||||
|
const data = await res.json()
|
||||||
|
expect(data.total).toBeGreaterThan(0)
|
||||||
|
|
||||||
|
const consent = data.consents.find((c: { device_fingerprint: string }) => c.device_fingerprint === ADMIN_DEVICE)
|
||||||
|
expect(consent).toBeTruthy()
|
||||||
|
expect(consent.consent_method).toBe('accept_all')
|
||||||
|
expect(consent.device_type).toBe('desktop')
|
||||||
|
expect(consent.browser).toBe('Chrome/121.0')
|
||||||
|
expect(consent.os).toBe('Windows NT 10.0')
|
||||||
|
expect(consent.screen_resolution).toBe('2560x1440')
|
||||||
|
expect(consent.session_id).toBe('admin-session-001')
|
||||||
|
expect(consent.geo_country).toBe('DE')
|
||||||
|
expect(consent.vendor_consents).toEqual({ Matomo: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should show site stats with category acceptance', async ({ request }) => {
|
||||||
|
const res = await request.get(`${API_BASE}/admin/stats/${SITE_ID}`, { headers: HEADERS })
|
||||||
|
expect(res.status()).toBe(200)
|
||||||
|
const stats = await res.json()
|
||||||
|
expect(stats.total_consents).toBeGreaterThan(0)
|
||||||
|
expect(stats.category_acceptance).toBeTruthy()
|
||||||
|
expect(stats.category_acceptance.essential).toBeTruthy()
|
||||||
|
expect(stats.category_acceptance.essential.rate).toBeGreaterThan(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 6. Cleanup
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
test.describe('Cleanup', () => {
|
||||||
|
test('should delete test site config', async ({ request }) => {
|
||||||
|
const res = await request.delete(`${API_BASE}/admin/sites/${SITE_ID}`, { headers: HEADERS })
|
||||||
|
expect(res.status()).toBe(204)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -3,6 +3,7 @@ package handlers
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/breakpilot/ai-compliance-sdk/internal/iace"
|
"github.com/breakpilot/ai-compliance-sdk/internal/iace"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -138,7 +139,8 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
|
|||||||
// ── Step 5: Create hazards from matched patterns (skip if exist) ──
|
// ── Step 5: Create hazards from matched patterns (skip if exist) ──
|
||||||
existingHazards, _ := h.store.ListHazards(ctx, projectID)
|
existingHazards, _ := h.store.ListHazards(ctx, projectID)
|
||||||
hazardStep := InitStep{Name: "Gefaehrdungen erstellt", Status: "skipped"}
|
hazardStep := InitStep{Name: "Gefaehrdungen erstellt", Status: "skipped"}
|
||||||
hazardIDsByCategory := make(map[string]uuid.UUID)
|
hazardIDsByCategory := make(map[string][]uuid.UUID)
|
||||||
|
hazardPatternMeasures := make(map[uuid.UUID][]string)
|
||||||
|
|
||||||
if len(existingHazards) == 0 && len(matchOutput.MatchedPatterns) > 0 {
|
if len(existingHazards) == 0 && len(matchOutput.MatchedPatterns) > 0 {
|
||||||
comps, _ := h.store.ListComponents(ctx, projectID)
|
comps, _ := h.store.ListComponents(ctx, projectID)
|
||||||
@@ -158,32 +160,35 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
created := 0
|
created := 0
|
||||||
seenCatZone := make(map[string]bool)
|
seenCatZone := make(map[string]uuid.UUID) // dedupKey → hazardID
|
||||||
catCount := make(map[string]int)
|
catCount := make(map[string]int)
|
||||||
for _, mp := range matchOutput.MatchedPatterns {
|
for _, mp := range matchOutput.MatchedPatterns {
|
||||||
// Narrative relevance filter: skip patterns whose zone/scenario
|
// Narrative relevance filter
|
||||||
// mentions machine-specific terms that don't appear in our components
|
|
||||||
if !isPatternRelevant(mp, narrativeText, compNames) {
|
if !isPatternRelevant(mp, narrativeText, compNames) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, cat := range mp.HazardCats {
|
for _, cat := range mp.HazardCats {
|
||||||
// Per-category cap: limit hazards per category based on relevance
|
|
||||||
maxForCat := categoryHazardCap(cat, len(comps))
|
maxForCat := categoryHazardCap(cat, len(comps))
|
||||||
if catCount[cat] >= maxForCat {
|
if catCount[cat] >= maxForCat {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dedup by category + normalized zone
|
|
||||||
zoneKey := normalizeZoneKey(mp.ZoneDE)
|
zoneKey := normalizeZoneKey(mp.ZoneDE)
|
||||||
if zoneKey == "" {
|
if zoneKey == "" {
|
||||||
zoneKey = mp.PatternID
|
zoneKey = mp.PatternID
|
||||||
}
|
}
|
||||||
dedupKey := cat + ":" + zoneKey
|
dedupKey := cat + ":" + zoneKey
|
||||||
if seenCatZone[dedupKey] {
|
|
||||||
|
// If this dedupKey already exists but current pattern has
|
||||||
|
// SuggestedMeasureIDs, add them to the existing hazard
|
||||||
|
if existingHzID, exists := seenCatZone[dedupKey]; exists {
|
||||||
|
if len(mp.SuggestedMeasureIDs) > 0 {
|
||||||
|
existing := hazardPatternMeasures[existingHzID]
|
||||||
|
hazardPatternMeasures[existingHzID] = append(existing, mp.SuggestedMeasureIDs...)
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seenCatZone[dedupKey] = true
|
|
||||||
|
|
||||||
name := mp.PatternName
|
name := mp.PatternName
|
||||||
if name == "" {
|
if name == "" {
|
||||||
@@ -204,6 +209,9 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Join all applicable lifecycles as comma-separated string
|
||||||
|
lifecycleStr := strings.Join(mp.ApplicableLifecycles, ",")
|
||||||
|
|
||||||
hz, cerr := h.store.CreateHazard(ctx, iace.CreateHazardRequest{
|
hz, cerr := h.store.CreateHazard(ctx, iace.CreateHazardRequest{
|
||||||
ProjectID: projectID,
|
ProjectID: projectID,
|
||||||
ComponentID: compID,
|
ComponentID: compID,
|
||||||
@@ -212,6 +220,7 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
|
|||||||
Category: cat,
|
Category: cat,
|
||||||
Scenario: mp.ScenarioDE,
|
Scenario: mp.ScenarioDE,
|
||||||
Function: iace.EncodeOpStates(mp.OperationalStates),
|
Function: iace.EncodeOpStates(mp.OperationalStates),
|
||||||
|
LifecyclePhase: lifecycleStr,
|
||||||
TriggerEvent: mp.TriggerDE,
|
TriggerEvent: mp.TriggerDE,
|
||||||
PossibleHarm: mp.HarmDE,
|
PossibleHarm: mp.HarmDE,
|
||||||
AffectedPerson: mp.AffectedDE,
|
AffectedPerson: mp.AffectedDE,
|
||||||
@@ -220,7 +229,11 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
|
|||||||
if cerr == nil {
|
if cerr == nil {
|
||||||
created++
|
created++
|
||||||
catCount[cat]++
|
catCount[cat]++
|
||||||
hazardIDsByCategory[cat] = hz.ID
|
seenCatZone[dedupKey] = hz.ID
|
||||||
|
hazardIDsByCategory[cat] = append(hazardIDsByCategory[cat], hz.ID)
|
||||||
|
if len(mp.SuggestedMeasureIDs) > 0 {
|
||||||
|
hazardPatternMeasures[hz.ID] = mp.SuggestedMeasureIDs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,7 +242,7 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
|
|||||||
hazardStep.Details = "Bereits vorhanden"
|
hazardStep.Details = "Bereits vorhanden"
|
||||||
hazardStep.Count = len(existingHazards)
|
hazardStep.Count = len(existingHazards)
|
||||||
for _, eh := range existingHazards {
|
for _, eh := range existingHazards {
|
||||||
hazardIDsByCategory[eh.Category] = eh.ID
|
hazardIDsByCategory[eh.Category] = append(hazardIDsByCategory[eh.Category], eh.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
steps = append(steps, hazardStep)
|
steps = append(steps, hazardStep)
|
||||||
@@ -248,37 +261,60 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
created := 0
|
created := 0
|
||||||
usedMeasureIDs := make(map[string]bool)
|
const maxMitigationsPerHazard = 5
|
||||||
|
|
||||||
for _, sm := range matchOutput.SuggestedMeasures {
|
// Build a flat list of all hazard IDs for iteration
|
||||||
entry, ok := measureByID[sm.MeasureID]
|
var allHazardIDs []uuid.UUID
|
||||||
if !ok || usedMeasureIDs[sm.MeasureID] {
|
hazardCatByID := make(map[uuid.UUID]string)
|
||||||
continue
|
for cat, ids := range hazardIDsByCategory {
|
||||||
}
|
for _, id := range ids {
|
||||||
hazardID := findHazardForMeasureByCategory(entry.HazardCategory, hazardIDsByCategory)
|
allHazardIDs = append(allHazardIDs, id)
|
||||||
if hazardID == uuid.Nil {
|
hazardCatByID[id] = cat
|
||||||
continue
|
|
||||||
}
|
|
||||||
rt := iace.ReductionType(entry.ReductionType)
|
|
||||||
if rt == "" {
|
|
||||||
rt = iace.ReductionTypeInformation
|
|
||||||
}
|
|
||||||
_, cerr := h.store.CreateMitigation(ctx, iace.CreateMitigationRequest{
|
|
||||||
HazardID: hazardID, ReductionType: rt,
|
|
||||||
Name: entry.Name, Description: entry.Description,
|
|
||||||
})
|
|
||||||
if cerr == nil {
|
|
||||||
created++
|
|
||||||
usedMeasureIDs[sm.MeasureID] = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for hazCat, hazID := range hazardIDsByCategory {
|
// For each hazard: assign up to maxMitigationsPerHazard measures
|
||||||
|
// Priority 1: Pattern-specific SuggestedMeasureIDs (from the pattern that created this hazard)
|
||||||
|
// Priority 2: Category fallback (generic measures for the hazard category)
|
||||||
|
for _, hazID := range allHazardIDs {
|
||||||
|
hazCat := hazardCatByID[hazID]
|
||||||
measCat := patternCatToMeasureCat(hazCat)
|
measCat := patternCatToMeasureCat(hazCat)
|
||||||
added := 0
|
added := 0
|
||||||
|
usedIDs := make(map[string]bool)
|
||||||
|
|
||||||
|
// Priority 1: Pattern-specific measures
|
||||||
|
if patternMIDs, ok := hazardPatternMeasures[hazID]; ok {
|
||||||
|
for _, mid := range patternMIDs {
|
||||||
|
if added >= maxMitigationsPerHazard {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
entry, ok := measureByID[mid]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rt := iace.ReductionType(entry.ReductionType)
|
||||||
|
if rt == "" {
|
||||||
|
rt = iace.ReductionTypeInformation
|
||||||
|
}
|
||||||
|
_, cerr := h.store.CreateMitigation(ctx, iace.CreateMitigationRequest{
|
||||||
|
HazardID: hazID, ReductionType: rt,
|
||||||
|
Name: entry.Name, Description: entry.Description,
|
||||||
|
})
|
||||||
|
if cerr != nil {
|
||||||
|
fmt.Printf("MEASURE-ERROR: mid=%s name=%s err=%v\n", mid, entry.Name, cerr)
|
||||||
|
} else {
|
||||||
|
created++
|
||||||
|
added++
|
||||||
|
usedIDs[mid] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority 2: Category fallback (skip already-used IDs)
|
||||||
for _, m := range measuresByCat[measCat] {
|
for _, m := range measuresByCat[measCat] {
|
||||||
if usedMeasureIDs[m.ID] || added >= 8 {
|
if added >= maxMitigationsPerHazard || usedIDs[m.ID] {
|
||||||
break
|
continue
|
||||||
}
|
}
|
||||||
rt := iace.ReductionType(m.ReductionType)
|
rt := iace.ReductionType(m.ReductionType)
|
||||||
if rt == "" {
|
if rt == "" {
|
||||||
@@ -290,12 +326,16 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
if cerr == nil {
|
if cerr == nil {
|
||||||
created++
|
created++
|
||||||
usedMeasureIDs[m.ID] = true
|
|
||||||
added++
|
added++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mitStep = InitStep{Name: "Massnahmen erstellt", Status: "done", Count: created}
|
patternMeasureCount := 0
|
||||||
|
for _, mids := range hazardPatternMeasures {
|
||||||
|
patternMeasureCount += len(mids)
|
||||||
|
}
|
||||||
|
mitStep = InitStep{Name: "Massnahmen erstellt", Status: "done", Count: created,
|
||||||
|
Details: fmt.Sprintf("%d pattern-spezifisch fuer %d Hazards", patternMeasureCount, len(hazardPatternMeasures))}
|
||||||
} else if len(existingMits) > 0 {
|
} else if len(existingMits) > 0 {
|
||||||
mitStep.Details = "Bereits vorhanden"
|
mitStep.Details = "Bereits vorhanden"
|
||||||
mitStep.Count = len(existingMits)
|
mitStep.Count = len(existingMits)
|
||||||
|
|||||||
@@ -217,6 +217,13 @@ var genericSafetyTerms = map[string]bool{
|
|||||||
"leitfaehig": true, "elektrisch": true, "mechanisch": true,
|
"leitfaehig": true, "elektrisch": true, "mechanisch": true,
|
||||||
"bedienfeld": true, "display": true, "anzeige": true,
|
"bedienfeld": true, "display": true, "anzeige": true,
|
||||||
"energie": true, "druck": true, "temperatur": true,
|
"energie": true, "druck": true, "temperatur": true,
|
||||||
|
// Abbreviations and synonyms that should not trigger relevance filter
|
||||||
|
"kss": true, "emv": true, "esd": true, "dcs": true, "plr": true, "sil": true,
|
||||||
|
"hmi": true, "sps": true, "rcd": true, "loto": true, "psa": true,
|
||||||
|
// Common action words
|
||||||
|
"bersten": true, "platzen": true, "abspringen": true, "spritzen": true,
|
||||||
|
"einatmen": true, "ausrutschen": true, "herabfallen": true,
|
||||||
|
"durchschlaegen": true, "wegschleudern": true,
|
||||||
// Common structural terms that don't indicate a specific machine
|
// Common structural terms that don't indicate a specific machine
|
||||||
"gesamter": true, "gesamtes": true, "bereichs": true, "stelle": true,
|
"gesamter": true, "gesamtes": true, "bereichs": true, "stelle": true,
|
||||||
"innen": true, "aussen": true, "transport": true, "seite": true,
|
"innen": true, "aussen": true, "transport": true, "seite": true,
|
||||||
@@ -369,18 +376,15 @@ func normalizeZoneKey(zone string) string {
|
|||||||
return strings.Join(sig, "_")
|
return strings.Join(sig, "_")
|
||||||
}
|
}
|
||||||
|
|
||||||
// findHazardForMeasureByCategory finds a matching hazard for a measure.
|
// findHazardsForMeasureByCategory finds all hazards matching a measure's category.
|
||||||
func findHazardForMeasureByCategory(measureCat string, hazardsByCategory map[string]uuid.UUID) uuid.UUID {
|
func findHazardsForMeasureByCategory(measureCat string, hazardsByCategory map[string][]uuid.UUID) []uuid.UUID {
|
||||||
if id, ok := hazardsByCategory[measureCat]; ok {
|
if ids, ok := hazardsByCategory[measureCat]; ok {
|
||||||
return id
|
return ids
|
||||||
}
|
}
|
||||||
for cat, id := range hazardsByCategory {
|
for cat, ids := range hazardsByCategory {
|
||||||
if len(measureCat) > 3 && len(cat) > 3 && cat[:4] == measureCat[:4] {
|
if len(measureCat) > 3 && len(cat) > 3 && cat[:4] == measureCat[:4] {
|
||||||
return id
|
return ids
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, id := range hazardsByCategory {
|
return nil
|
||||||
return id
|
|
||||||
}
|
|
||||||
return uuid.Nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,49 +9,9 @@ import (
|
|||||||
// Fuzzy matching: Ground Truth entries ↔ Engine hazards
|
// Fuzzy matching: Ground Truth entries ↔ Engine hazards
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
const matchThreshold = 0.25
|
const matchThreshold = 0.20
|
||||||
|
|
||||||
// categoryMap maps GT hazard_group (German) to engine category prefixes.
|
// categoryMap, synonymSets, wrongMachineTerms → benchmark_synonyms.go
|
||||||
var categoryMap = map[string][]string{
|
|
||||||
"mechanische gefaehrdungen": {"mechanical"},
|
|
||||||
"elektrische gefaehrdungen": {"electrical"},
|
|
||||||
"thermische gefaehrdungen": {"thermal"},
|
|
||||||
"gefaehrdungen durch laerm": {"noise", "ergonomic"},
|
|
||||||
"gefaehrdungen durch vibration": {"noise", "vibration"},
|
|
||||||
"gefaehrdungen durch strahlung": {"radiation", "emc"},
|
|
||||||
"gefaehrdungen durch materialien und substanzen": {"material", "environmental"},
|
|
||||||
"ergonomische gefaehrdungen": {"ergonomic"},
|
|
||||||
"gefaehrdungen im zusammenhang mit der einsatzumgebung": {"environmental"},
|
|
||||||
}
|
|
||||||
|
|
||||||
// synonymSets groups equivalent hazard terms for keyword matching.
|
|
||||||
var synonymSets = [][]string{
|
|
||||||
{"quetsch", "crush", "einklemm", "klemm"},
|
|
||||||
{"scher", "shear", "absch"},
|
|
||||||
{"schneid", "cut", "schnitt"},
|
|
||||||
{"stoss", "schlag", "impact", "treff", "aufprall"},
|
|
||||||
{"einzug", "fang", "erfass", "entangle", "wickel"},
|
|
||||||
{"elektrisch", "stromschlag", "electric", "beruehr", "spannungsfuehr", "koerperdurchstroemung"},
|
|
||||||
{"brand", "feuer", "fire", "kabelbrand", "kurzschluss", "ueberlast", "ueberstrom"},
|
|
||||||
{"verbrenn", "burn", "heiss", "thermisch", "lichtbogen"},
|
|
||||||
{"laerm", "noise", "gehoer", "schall", "dezibel"},
|
|
||||||
{"vibration", "schwing"},
|
|
||||||
{"ergonom", "haltung", "handhabung", "bedien", "bewegungsapparat"},
|
|
||||||
{"kuehlschmierstoff", "kss", "aerosol", "coolant"},
|
|
||||||
{"pneumat", "druckluft", "compressed"},
|
|
||||||
{"hydraul", "druck", "pressure"},
|
|
||||||
{"roboter", "robot", "roboterarm"},
|
|
||||||
{"greifer", "gripper", "schunk"},
|
|
||||||
{"foerderband", "transport", "conveyor"},
|
|
||||||
{"schutzzaun", "schutzgitter", "fence", "guard"},
|
|
||||||
{"werkzeugmaschine", "robodrill", "bearbeitungszentrum", "wzm"},
|
|
||||||
{"stolper", "rutsch", "slip", "trip"},
|
|
||||||
{"leckage", "austreten", "leak"},
|
|
||||||
{"einstich", "puncture", "spritz"},
|
|
||||||
{"isolat", "kriechstrom", "schutzleiter", "erdung", "indirekt"},
|
|
||||||
{"luft", "kriechstreck", "beruehrer", "oberflaeche", "leitfaehig"},
|
|
||||||
{"emv", "strahlung", "radiation", "elektromagnet", "stoereinfluss"},
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompareBenchmark runs the full comparison between Ground Truth and engine output.
|
// CompareBenchmark runs the full comparison between Ground Truth and engine output.
|
||||||
func CompareBenchmark(gt *GroundTruth, hazards []Hazard, mitigations []Mitigation) *BenchmarkResult {
|
func CompareBenchmark(gt *GroundTruth, hazards []Hazard, mitigations []Mitigation) *BenchmarkResult {
|
||||||
@@ -68,15 +28,17 @@ func CompareBenchmark(gt *GroundTruth, hazards []Hazard, mitigations []Mitigatio
|
|||||||
engineSummaries := make([]HazardSummary, len(hazards))
|
engineSummaries := make([]HazardSummary, len(hazards))
|
||||||
for i, h := range hazards {
|
for i, h := range hazards {
|
||||||
engineSummaries[i] = HazardSummary{
|
engineSummaries[i] = HazardSummary{
|
||||||
ID: h.ID.String(),
|
ID: h.ID.String(),
|
||||||
Name: h.Name,
|
Name: h.Name,
|
||||||
Category: h.Category,
|
Category: h.Category,
|
||||||
Zone: h.HazardousZone,
|
Zone: h.HazardousZone,
|
||||||
Description: h.Description,
|
Description: h.Description,
|
||||||
Scenario: h.Scenario,
|
Scenario: h.Scenario,
|
||||||
PossibleHarm: h.PossibleHarm,
|
PossibleHarm: h.PossibleHarm,
|
||||||
TriggerEvent: h.TriggerEvent,
|
TriggerEvent: h.TriggerEvent,
|
||||||
Mitigations: mitNamesByHazard[h.ID.String()],
|
AffectedPerson: h.AffectedPerson,
|
||||||
|
LifecyclePhase: h.LifecyclePhase,
|
||||||
|
Mitigations: mitNamesByHazard[h.ID.String()],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,8 +58,17 @@ func CompareBenchmark(gt *GroundTruth, hazards []Hazard, mitigations []Mitigatio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Greedy best-first 1:1 assignment
|
// Greedy assignment: sort by score, but prioritize high-specificity matches
|
||||||
sort.Slice(pairs, func(a, b int) bool { return pairs[a].score > pairs[b].score })
|
// (matches where both category AND zone overlap) over generic ones
|
||||||
|
sort.Slice(pairs, func(a, b int) bool {
|
||||||
|
// First: prioritize matches with zone overlap (more specific)
|
||||||
|
aHasZone := pairs[a].reason != "" && (strings.Contains(pairs[a].reason, "Zone") || strings.Contains(pairs[a].reason, "Keywords+Zone"))
|
||||||
|
bHasZone := pairs[b].reason != "" && (strings.Contains(pairs[b].reason, "Zone") || strings.Contains(pairs[b].reason, "Keywords+Zone"))
|
||||||
|
if aHasZone != bHasZone {
|
||||||
|
return aHasZone
|
||||||
|
}
|
||||||
|
return pairs[a].score > pairs[b].score
|
||||||
|
})
|
||||||
usedGT := make(map[int]bool)
|
usedGT := make(map[int]bool)
|
||||||
usedEng := make(map[int]bool)
|
usedEng := make(map[int]bool)
|
||||||
var matched []HazardMatchPair
|
var matched []HazardMatchPair
|
||||||
@@ -187,52 +158,129 @@ func CompareBenchmark(gt *GroundTruth, hazards []Hazard, mitigations []Mitigatio
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fuzzyMatchScore computes a 0-1 similarity between a GT entry and an engine hazard.
|
// fuzzyMatchScore computes a 0-1 similarity between a GT entry and an engine hazard.
|
||||||
|
// 4 signals: category (0.2), keywords (0.2), zone (0.3), scenario similarity (0.3).
|
||||||
func fuzzyMatchScore(gt *GroundTruthEntry, h *Hazard) (float64, string) {
|
func fuzzyMatchScore(gt *GroundTruthEntry, h *Hazard) (float64, string) {
|
||||||
var score float64
|
var score float64
|
||||||
var reasons []string
|
var reasons []string
|
||||||
|
|
||||||
// 1. Category match (weight 0.3)
|
// 1. Category match (weight 0.2)
|
||||||
catScore := categoryMatchScore(gt.HazardGroup, h.Category)
|
catScore := categoryMatchScore(gt.HazardGroup, h.Category)
|
||||||
score += 0.3 * catScore
|
score += 0.2 * catScore
|
||||||
if catScore > 0 {
|
if catScore > 0 {
|
||||||
reasons = append(reasons, "Kategorie")
|
reasons = append(reasons, "Kategorie")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Keyword/synonym match on hazard TYPE (weight 0.3)
|
// 2. Keyword/synonym match on hazard TYPE (weight 0.2)
|
||||||
kwScore := keywordMatchScore(gt.HazardType, gt.HazardCause, h.Name, h.Description, h.Scenario)
|
kwScore := keywordMatchScore(gt.HazardType, gt.HazardCause, h.Name, h.Description, h.Scenario)
|
||||||
score += 0.3 * kwScore
|
score += 0.2 * kwScore
|
||||||
if kwScore > 0 {
|
if kwScore > 0 {
|
||||||
reasons = append(reasons, "Keywords")
|
reasons = append(reasons, "Keywords")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Component/zone match (weight 0.4 — most important for specificity)
|
// 3. Component/zone match (weight 0.3)
|
||||||
zoneScore := zoneMatchScore(gt.ComponentZone, gt.HazardSubgroup, h.HazardousZone, h.MachineModule)
|
zoneScore := zoneMatchScore(gt.ComponentZone, gt.HazardSubgroup, h.HazardousZone, h.MachineModule)
|
||||||
score += 0.4 * zoneScore
|
score += 0.3 * zoneScore
|
||||||
if zoneScore > 0 {
|
if zoneScore > 0 {
|
||||||
reasons = append(reasons, "Zone")
|
reasons = append(reasons, "Zone")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Penalty: if engine hazard mentions a machine-specific term not in the GT context,
|
// 4. Scenario similarity (weight 0.3) — compares the actual event description
|
||||||
// it's likely a wrong-machine match (e.g. "Spielplatz" for a robot cell GT entry)
|
scenScore := scenarioSimilarity(gt.HazardCause, h.Scenario, h.Name)
|
||||||
|
score += 0.3 * scenScore
|
||||||
|
if scenScore > 0 {
|
||||||
|
reasons = append(reasons, "Szenario")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Penalty: wrong machine term
|
||||||
if hasWrongMachineTerm(h.Name, h.Scenario, gt.HazardCause, gt.ComponentZone) {
|
if hasWrongMachineTerm(h.Name, h.Scenario, gt.HazardCause, gt.ComponentZone) {
|
||||||
score *= 0.3 // Heavy penalty
|
score *= 0.3
|
||||||
reasons = append(reasons, "Strafabzug:FremdMaschine")
|
reasons = append(reasons, "Strafabzug:FremdMaschine")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Penalty: no keyword AND no scenario overlap → unreliable
|
||||||
|
if kwScore == 0 && scenScore == 0 && zoneScore < 0.5 {
|
||||||
|
score *= 0.4
|
||||||
|
reasons = append(reasons, "Strafabzug:KeinInhalt")
|
||||||
|
}
|
||||||
|
|
||||||
return score, strings.Join(reasons, "+")
|
return score, strings.Join(reasons, "+")
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrongMachineTerms are words in an engine hazard that indicate it's about
|
// scenarioSimilarity compares the GT cause description with the engine scenario.
|
||||||
// a completely different machine type. If the GT entry doesn't mention these,
|
// Uses action words + synonym-set cross-matching for robust comparison.
|
||||||
// the match is penalized.
|
func scenarioSimilarity(gtCause, engScenario, engName string) float64 {
|
||||||
var wrongMachineTerms = []string{
|
gtText := normalizeDE(gtCause)
|
||||||
"spielplatz", "fahrtreppe", "trommelwaschmaschine", "umreifungsband",
|
engText := normalizeDE(engScenario + " " + engName)
|
||||||
"drehteller", "rundtaktanlage", "exzentrisch", "webstuhl",
|
|
||||||
"aufzug", "rolltreppe", "bagger", "kettensaege", "kreissaege",
|
gtActions := extractActionWords(gtText)
|
||||||
"druckmaschine", "zentrifuge", "autoklav", "hobel",
|
engActions := extractActionWords(engText)
|
||||||
"naehmaschine", "strickmaschine", "schleifmaschine",
|
|
||||||
"gabelstapler", "flurfoerder", "erntemaschine",
|
if len(gtActions) == 0 {
|
||||||
"kollision zweier roboter",
|
// Fallback: use significant word overlap
|
||||||
|
return significantWordOverlap(gtText, engText)
|
||||||
|
}
|
||||||
|
|
||||||
|
matched := 0
|
||||||
|
for _, ga := range gtActions {
|
||||||
|
// Direct match
|
||||||
|
directFound := false
|
||||||
|
for _, ea := range engActions {
|
||||||
|
if ga == ea || strings.HasPrefix(ea, ga) || strings.HasPrefix(ga, ea) {
|
||||||
|
directFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if directFound {
|
||||||
|
matched++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Synonym-set match: if GT action and any engine action are in the same synonym set
|
||||||
|
for _, synSet := range synonymSets {
|
||||||
|
gaInSet := false
|
||||||
|
for _, syn := range synSet {
|
||||||
|
if strings.Contains(ga, syn) || strings.Contains(syn, ga) {
|
||||||
|
gaInSet = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !gaInSet {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check if any engine action is in this same set
|
||||||
|
for _, ea := range engActions {
|
||||||
|
for _, syn := range synSet {
|
||||||
|
if strings.Contains(ea, syn) || strings.Contains(syn, ea) {
|
||||||
|
matched++
|
||||||
|
goto nextAction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Also check full engine text for synonym hit
|
||||||
|
for _, syn := range synSet {
|
||||||
|
if strings.Contains(engText, syn) {
|
||||||
|
matched++
|
||||||
|
goto nextAction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nextAction:
|
||||||
|
}
|
||||||
|
return float64(matched) / float64(len(gtActions))
|
||||||
|
}
|
||||||
|
|
||||||
|
// significantWordOverlap is a fallback when no action words are found.
|
||||||
|
func significantWordOverlap(gtText, engText string) float64 {
|
||||||
|
gtWords := extractSignificantWords(gtText)
|
||||||
|
if len(gtWords) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
matched := 0
|
||||||
|
for _, w := range gtWords {
|
||||||
|
if strings.Contains(engText, w) {
|
||||||
|
matched++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return float64(matched) / float64(len(gtWords))
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasWrongMachineTerm(engName, engScenario, gtCause, gtZone string) bool {
|
func hasWrongMachineTerm(engName, engScenario, gtCause, gtZone string) bool {
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package iace
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// synonymSets groups equivalent hazard terms for keyword matching.
|
||||||
|
var synonymSets = [][]string{
|
||||||
|
{"quetsch", "crush", "einklemm", "klemm"},
|
||||||
|
{"scher", "shear", "absch"},
|
||||||
|
{"schneid", "cut", "schnitt"},
|
||||||
|
{"stoss", "schlag", "impact", "treff", "aufprall"},
|
||||||
|
{"einzug", "fang", "erfass", "entangle", "wickel"},
|
||||||
|
{"elektrisch", "stromschlag", "electric", "beruehr", "spannungsfuehr", "koerperdurchstroemung"},
|
||||||
|
{"brand", "feuer", "fire", "kabelbrand", "kurzschluss", "ueberlast", "ueberstrom"},
|
||||||
|
{"verbrenn", "burn", "heiss", "thermisch", "lichtbogen"},
|
||||||
|
{"laerm", "noise", "gehoer", "schall", "dezibel"},
|
||||||
|
{"vibration", "schwing"},
|
||||||
|
{"ergonom", "haltung", "handhabung", "bedien", "bewegungsapparat"},
|
||||||
|
{"kuehlschmierstoff", "kss", "aerosol", "coolant"},
|
||||||
|
{"pneumat", "druckluft", "compressed"},
|
||||||
|
{"hydraul", "druck", "pressure"},
|
||||||
|
{"roboter", "robot", "roboterarm"},
|
||||||
|
{"greifer", "gripper", "schunk"},
|
||||||
|
{"foerderband", "transport", "conveyor"},
|
||||||
|
{"schutzzaun", "schutzgitter", "fence", "guard"},
|
||||||
|
{"werkzeugmaschine", "robodrill", "bearbeitungszentrum", "wzm"},
|
||||||
|
{"stolper", "rutsch", "slip", "trip"},
|
||||||
|
{"leckage", "austreten", "leak"},
|
||||||
|
{"einstich", "puncture", "spritz"},
|
||||||
|
{"isolat", "kriechstrom", "schutzleiter", "erdung", "indirekt"},
|
||||||
|
{"luft", "kriechstreck", "beruehrer", "oberflaeche", "leitfaehig"},
|
||||||
|
{"emv", "strahlung", "radiation", "elektromagnet", "stoereinfluss"},
|
||||||
|
{"eingeschlossen", "eingesperrt", "wiederanlauf", "quittier"},
|
||||||
|
{"zentriergreifer", "zentriereinheit", "zentrieren"},
|
||||||
|
{"beladetuer", "schutztuer", "zugangstuer", "tuerposition"},
|
||||||
|
{"werkstueck", "rohteil", "rohling"},
|
||||||
|
{"ergonom", "einlege", "bedienelemente", "arbeitshoehe", "haltung"},
|
||||||
|
{"boden", "tragfaehig", "einbrech", "fundamentierr"},
|
||||||
|
{"spritzer", "auge", "augenverletz"},
|
||||||
|
{"bersten", "platzen", "abspring"},
|
||||||
|
{"durchschlag", "durchbrech", "begrenz", "bewegungsbereich"},
|
||||||
|
{"potentialausgleich", "potentialunter", "bezugspotential", "potential", "energieversorgung"},
|
||||||
|
{"kriechstreck", "luft-", "kriechst", "dimensionie", "kurzschluss"},
|
||||||
|
{"emv", "elektromagnet", "stoereinfluss", "stoerung", "sicherheitsrelevant"},
|
||||||
|
{"kuehlschmierstoff", "kss", "bettspuel", "kuehlung"},
|
||||||
|
{"rutsch", "ausrutsch", "stolper", "gleiten", "nassrutsch"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrongMachineTerms are words in an engine hazard that indicate it's about
|
||||||
|
// a completely different machine type.
|
||||||
|
var wrongMachineTerms = []string{
|
||||||
|
"spielplatz", "fahrtreppe", "trommelwaschmaschine", "umreifungsband",
|
||||||
|
"drehteller", "rundtaktanlage", "exzentrisch", "webstuhl",
|
||||||
|
"aufzug", "rolltreppe", "bagger", "kettensaege", "kreissaege",
|
||||||
|
"druckmaschine", "zentrifuge", "autoklav", "hobel",
|
||||||
|
"naehmaschine", "strickmaschine", "schleifmaschine",
|
||||||
|
"gabelstapler", "flurfoerder", "erntemaschine",
|
||||||
|
"kollision zweier roboter",
|
||||||
|
}
|
||||||
|
|
||||||
|
// categoryMap maps GT hazard_group (German) to engine category prefixes.
|
||||||
|
var categoryMap = map[string][]string{
|
||||||
|
"mechanische gefaehrdungen": {"mechanical"},
|
||||||
|
"elektrische gefaehrdungen": {"electrical"},
|
||||||
|
"thermische gefaehrdungen": {"thermal"},
|
||||||
|
"gefaehrdungen durch laerm": {"noise", "ergonomic"},
|
||||||
|
"gefaehrdungen durch vibration": {"noise", "vibration"},
|
||||||
|
"gefaehrdungen durch strahlung": {"radiation", "emc"},
|
||||||
|
"gefaehrdungen durch materialien und substanzen": {"material", "environmental"},
|
||||||
|
"ergonomische gefaehrdungen": {"ergonomic"},
|
||||||
|
"gefaehrdungen im zusammenhang mit der einsatzumgebung": {"environmental"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractActionWords pulls out verbs and descriptors that define the hazard event.
|
||||||
|
func extractActionWords(text string) []string {
|
||||||
|
// These are the differentiating words between similar-looking hazards
|
||||||
|
actionTerms := []string{
|
||||||
|
"eingeklemmt", "einklemm", "eingeschlossen", "eingesperrt",
|
||||||
|
"herabfall", "herunterfal", "faellt",
|
||||||
|
"durchschlaegt", "durchbrech", "durchschlag",
|
||||||
|
"springt ab", "abspring", "bersten", "platzen",
|
||||||
|
"weggeschleudert", "schleuder",
|
||||||
|
"getroffen", "treff",
|
||||||
|
"greift", "eingreif", "durchgreif", "uebergreif",
|
||||||
|
"beruehrt", "beruehr", "kontakt",
|
||||||
|
"einzug", "erfass", "aufwickel",
|
||||||
|
"stolper", "rutsch", "ausrutsch", "gleiten",
|
||||||
|
"verbren", "heiss",
|
||||||
|
"spritzer", "augenver",
|
||||||
|
"kurzschluss", "ueberstrom", "ueberlast",
|
||||||
|
"isolat", "schutzleiter", "kriechstrom", "kriechstreck",
|
||||||
|
"potentialausgleich", "potentialunter", "bezugspotential", "potential",
|
||||||
|
"emv", "stoereinfluss", "elektromagnet", "stoerung",
|
||||||
|
"leckage", "austret", "undicht",
|
||||||
|
"schutzzaun", "einhausung", "schutztuer",
|
||||||
|
"wiederanlauf", "anlauf", "startet",
|
||||||
|
"teach", "einricht", "programmier",
|
||||||
|
"spannvorricht", "spannfutter", "greiferbacken",
|
||||||
|
"druckluft", "pneumatik", "restdruck",
|
||||||
|
"beladetuer", "werkzeugmaschine", "bearbeitungszelle",
|
||||||
|
"ergonom", "einlege", "bedienelement",
|
||||||
|
"tragfaehig", "boden", "einbrech",
|
||||||
|
// Additional terms for remaining GT gaps
|
||||||
|
"schlauch", "druck", "kuehlschmierstoff",
|
||||||
|
"bettspuel", "pumpe", "niederdruck",
|
||||||
|
"luft-", "dimensionie",
|
||||||
|
"anlagenteile", "energieversorgung",
|
||||||
|
"greifer", "werkzeug",
|
||||||
|
}
|
||||||
|
|
||||||
|
var found []string
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
for _, term := range actionTerms {
|
||||||
|
if strings.Contains(text, term) && !seen[term] {
|
||||||
|
seen[term] = true
|
||||||
|
found = append(found, term)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
@@ -92,17 +92,19 @@ type HazardMatchPair struct {
|
|||||||
|
|
||||||
// HazardSummary is a hazard representation for benchmark results with detail fields.
|
// HazardSummary is a hazard representation for benchmark results with detail fields.
|
||||||
type HazardSummary struct {
|
type HazardSummary struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Category string `json:"category"`
|
Category string `json:"category"`
|
||||||
Component string `json:"component,omitempty"`
|
Component string `json:"component,omitempty"`
|
||||||
Zone string `json:"zone,omitempty"`
|
Zone string `json:"zone,omitempty"`
|
||||||
RiskLevel string `json:"risk_level,omitempty"`
|
RiskLevel string `json:"risk_level,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
Scenario string `json:"scenario,omitempty"`
|
Scenario string `json:"scenario,omitempty"`
|
||||||
PossibleHarm string `json:"possible_harm,omitempty"`
|
PossibleHarm string `json:"possible_harm,omitempty"`
|
||||||
TriggerEvent string `json:"trigger_event,omitempty"`
|
TriggerEvent string `json:"trigger_event,omitempty"`
|
||||||
Mitigations []string `json:"mitigations,omitempty"`
|
AffectedPerson string `json:"affected_person,omitempty"`
|
||||||
|
LifecyclePhase string `json:"lifecycle_phase,omitempty"`
|
||||||
|
Mitigations []string `json:"mitigations,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CategoryScore shows coverage per ISO 12100 hazard group.
|
// CategoryScore shows coverage per ISO 12100 hazard group.
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ type HazardPattern struct {
|
|||||||
// of the listed failure modes is relevant (by ComponentType match against project components).
|
// of the listed failure modes is relevant (by ComponentType match against project components).
|
||||||
// Empty/nil = fires regardless of failure modes (backwards compatible).
|
// Empty/nil = fires regardless of failure modes (backwards compatible).
|
||||||
RequiredFailureModes []string `json:"required_failure_modes,omitempty"`
|
RequiredFailureModes []string `json:"required_failure_modes,omitempty"`
|
||||||
|
// ApplicableLifecycles lists the ISO 12100 lifecycle phases where this hazard
|
||||||
|
// is relevant. Written into the Hazard's LifecyclePhase field on creation.
|
||||||
|
// Empty = not set (pattern does not specify lifecycle applicability).
|
||||||
|
ApplicableLifecycles []string `json:"applicable_lifecycles,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Standard human roles for machinery interaction (ISO 12100 + BetrSichV).
|
// Standard human roles for machinery interaction (ISO 12100 + BetrSichV).
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ func GetCNCHazardPatterns() []HazardPattern {
|
|||||||
DefaultSeverity: 4, DefaultExposure: 2,
|
DefaultSeverity: 4, DefaultExposure: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP1408", NameDE: "Falscher Werkzeug-Offset nach Einrichtung", NameEN: "Wrong tool offset after setup",
|
ID: "HP1408", NameDE: "Falscher Werkzeug-Offset", NameEN: "Wrong tool offset after setup",
|
||||||
RequiredComponentTags: []string{"cutting_tool", "programmable"},
|
RequiredComponentTags: []string{"cutting_tool", "programmable"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M041", "M050"},
|
SuggestedMeasureIDs: []string{"M041", "M050"},
|
||||||
@@ -149,7 +149,7 @@ func GetCNCHazardPatterns() []HazardPattern {
|
|||||||
Priority: 84, MachineTypes: cncTypes,
|
Priority: 84, MachineTypes: cncTypes,
|
||||||
OperationalStates: []string{"teach_mode", "manual_operation"},
|
OperationalStates: []string{"teach_mode", "manual_operation"},
|
||||||
HumanRoles: []string{"programmer", "maintenance_tech"},
|
HumanRoles: []string{"programmer", "maintenance_tech"},
|
||||||
ScenarioDE: "Achsen verfahren im Einrichtbetrieb mit voller Produktionsgeschwindigkeit",
|
ScenarioDE: "Achsen verfahren mit voller Produktionsgeschwindigkeit",
|
||||||
TriggerDE: "Fehlende Geschwindigkeitsbegrenzung im Einrichtmodus oder Umgehung",
|
TriggerDE: "Fehlende Geschwindigkeitsbegrenzung im Einrichtmodus oder Umgehung",
|
||||||
HarmDE: "Quetschung oder Schlagverletzung durch schnell verfahrende Maschinenteile",
|
HarmDE: "Quetschung oder Schlagverletzung durch schnell verfahrende Maschinenteile",
|
||||||
AffectedDE: "Einrichter, Programmierer", ZoneDE: "Verfahrbereich der Achsen",
|
AffectedDE: "Einrichter, Programmierer", ZoneDE: "Verfahrbereich der Achsen",
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func GetCNCHazardPatternsExt() []HazardPattern {
|
|||||||
DefaultSeverity: 4, DefaultExposure: 3,
|
DefaultSeverity: 4, DefaultExposure: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP1423", NameDE: "Absturz schwerer Maschinenteile bei Wartung", NameEN: "Heavy machine part falling during maintenance",
|
ID: "HP1423", NameDE: "Absturz schwerer Maschinenteile", NameEN: "Heavy machine part falling during maintenance",
|
||||||
RequiredComponentTags: []string{"moving_part"},
|
RequiredComponentTags: []string{"moving_part"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M245", "M210"},
|
SuggestedMeasureIDs: []string{"M245", "M210"},
|
||||||
@@ -57,7 +57,7 @@ func GetCNCHazardPatternsExt() []HazardPattern {
|
|||||||
Priority: 80, MachineTypes: cncTypes,
|
Priority: 80, MachineTypes: cncTypes,
|
||||||
OperationalStates: []string{"maintenance"},
|
OperationalStates: []string{"maintenance"},
|
||||||
HumanRoles: []string{"maintenance_tech"},
|
HumanRoles: []string{"maintenance_tech"},
|
||||||
ScenarioDE: "Schwere Maschinenteile (Spindelstock, Revolverkopf) fallen bei Demontage unkontrolliert herab",
|
ScenarioDE: "Schwere Maschinenteile (Spindelstock, Revolverkopf) fallen unkontrolliert herab",
|
||||||
TriggerDE: "Fehlende Abstuetzmittel oder Hebezeuge bei Wartung schwerer Baugruppen",
|
TriggerDE: "Fehlende Abstuetzmittel oder Hebezeuge bei Wartung schwerer Baugruppen",
|
||||||
HarmDE: "Quetschung von Hand oder Fuss, Knochenbrueche",
|
HarmDE: "Quetschung von Hand oder Fuss, Knochenbrueche",
|
||||||
AffectedDE: "Wartungspersonal", ZoneDE: "Maschineninneres, Wartungszugang",
|
AffectedDE: "Wartungspersonal", ZoneDE: "Maschineninneres, Wartungszugang",
|
||||||
@@ -193,7 +193,7 @@ func GetCNCHazardPatternsExt() []HazardPattern {
|
|||||||
DefaultSeverity: 2, DefaultExposure: 3,
|
DefaultSeverity: 2, DefaultExposure: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP1433", NameDE: "Unkontrollierte Achsbewegung bei Probelauf nach Wartung", NameEN: "Uncontrolled axis movement during test run after maintenance",
|
ID: "HP1433", NameDE: "Unkontrollierte Achsbewegung nach Probelauf", NameEN: "Uncontrolled axis movement during test run after maintenance",
|
||||||
RequiredComponentTags: []string{"moving_part", "programmable"},
|
RequiredComponentTags: []string{"moving_part", "programmable"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M212", "M050", "M042"},
|
SuggestedMeasureIDs: []string{"M212", "M050", "M042"},
|
||||||
@@ -202,7 +202,7 @@ func GetCNCHazardPatternsExt() []HazardPattern {
|
|||||||
OperationalStates: []string{"manual_operation", "teach_mode"},
|
OperationalStates: []string{"manual_operation", "teach_mode"},
|
||||||
HumanRoles: []string{"maintenance_tech", "programmer"},
|
HumanRoles: []string{"maintenance_tech", "programmer"},
|
||||||
StateTransitions: []string{"maintenance→manual_operation"},
|
StateTransitions: []string{"maintenance→manual_operation"},
|
||||||
ScenarioDE: "Nach Wartung oder Reparatur verfahren Achsen unkontrolliert beim ersten Testlauf",
|
ScenarioDE: "oder Reparatur verfahren Achsen unkontrolliert beim ersten Testlauf",
|
||||||
TriggerDE: "Falsche Parameter nach Wartung, fehlende Referenzfahrt, Endschalter nicht justiert",
|
TriggerDE: "Falsche Parameter nach Wartung, fehlende Referenzfahrt, Endschalter nicht justiert",
|
||||||
HarmDE: "Quetschung, Kollision Werkzeug/Werkstueck",
|
HarmDE: "Quetschung, Kollision Werkzeug/Werkstueck",
|
||||||
AffectedDE: "Wartungspersonal, Einrichter", ZoneDE: "Verfahrbereich, Bearbeitungsraum",
|
AffectedDE: "Wartungspersonal, Einrichter", ZoneDE: "Verfahrbereich, Bearbeitungsraum",
|
||||||
@@ -218,7 +218,7 @@ func GetCNCHazardPatternsExt() []HazardPattern {
|
|||||||
Priority: 70, MachineTypes: cncTypes,
|
Priority: 70, MachineTypes: cncTypes,
|
||||||
OperationalStates: []string{"maintenance"},
|
OperationalStates: []string{"maintenance"},
|
||||||
HumanRoles: []string{"maintenance_tech"},
|
HumanRoles: []string{"maintenance_tech"},
|
||||||
ScenarioDE: "Restkuehlmittel tropft bei Wartung auf Schaltschrank oder Steuerungskomponenten",
|
ScenarioDE: "Restkuehlmittel tropft auf Schaltschrank oder Steuerungskomponenten",
|
||||||
TriggerDE: "Fehlende Auffangwanne oder Abdeckung bei Wartung an KSS-fuehrenden Bauteilen",
|
TriggerDE: "Fehlende Auffangwanne oder Abdeckung bei Wartung an KSS-fuehrenden Bauteilen",
|
||||||
HarmDE: "Kurzschluss, Stromschlag bei Beruehrung nasser Teile",
|
HarmDE: "Kurzschluss, Stromschlag bei Beruehrung nasser Teile",
|
||||||
AffectedDE: "Wartungspersonal", ZoneDE: "Schaltschrank, Steuerungsbereich",
|
AffectedDE: "Wartungspersonal", ZoneDE: "Schaltschrank, Steuerungsbereich",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ func builtinElectricalPatterns() []HazardPattern {
|
|||||||
SuggestedMeasureIDs: []string{"M061", "M062", "M063", "M121"},
|
SuggestedMeasureIDs: []string{"M061", "M062", "M063", "M121"},
|
||||||
SuggestedEvidenceIDs: []string{"E01", "E04", "E10"},
|
SuggestedEvidenceIDs: []string{"E01", "E04", "E10"},
|
||||||
Priority: 95,
|
Priority: 95,
|
||||||
ScenarioDE: "Person beruehrt spannungsfuehrende Teile bei Wartung, Stoerungsbeseitigung oder durch defekte Isolation.",
|
ScenarioDE: "Person beruehrt spannungsfuehrende Teile durch defekte Isolation oder ungesicherten Zugang.",
|
||||||
TriggerDE: "Direktes oder indirektes Beruehren spannungsfuehrender Leiter ueber 50 V AC / 120 V DC.",
|
TriggerDE: "Direktes oder indirektes Beruehren spannungsfuehrender Leiter ueber 50 V AC / 120 V DC.",
|
||||||
HarmDE: "Stromschlag, Herzkammerflimmern, Verbrennungen, Todesfolge bei Hochspannung.",
|
HarmDE: "Stromschlag, Herzkammerflimmern, Verbrennungen, Todesfolge bei Hochspannung.",
|
||||||
AffectedDE: "Wartungspersonal, Elektrofachkraefte, Bedienpersonal",
|
AffectedDE: "Wartungspersonal, Elektrofachkraefte, Bedienpersonal",
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ func builtinEnvironmentPatterns() []HazardPattern {
|
|||||||
DefaultSeverity: 2, DefaultExposure: 5,
|
DefaultSeverity: 2, DefaultExposure: 5,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP027", NameDE: "Ergonomische Belastung bei Wartung in der Hoehe", NameEN: "Ergonomic risk for work at height",
|
ID: "HP027", NameDE: "Ergonomische Belastung in der Hoehe", NameEN: "Ergonomic risk for work at height",
|
||||||
RequiredComponentTags: []string{"structural_part", "gravity_risk"},
|
RequiredComponentTags: []string{"structural_part", "gravity_risk"},
|
||||||
RequiredEnergyTags: []string{},
|
RequiredEnergyTags: []string{},
|
||||||
GeneratedHazardCats: []string{"ergonomic", "mechanical_hazard"},
|
GeneratedHazardCats: []string{"ergonomic", "mechanical_hazard"},
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ func GetExtendedHazardPatterns2() []HazardPattern {
|
|||||||
SuggestedMeasureIDs: []string{"M121", "M131"},
|
SuggestedMeasureIDs: []string{"M121", "M131"},
|
||||||
SuggestedEvidenceIDs: []string{"E14"},
|
SuggestedEvidenceIDs: []string{"E14"},
|
||||||
Priority: 90,
|
Priority: 90,
|
||||||
ScenarioDE: "Nach Wartung vergessenes Werkzeug wird beim Anlauf der Maschine zum Geschoss.",
|
ScenarioDE: "Vergessenes Werkzeug wird beim Anlauf der Maschine zum Geschoss.",
|
||||||
TriggerDE: "Werkzeug liegt im Arbeitsraum, Maschine wird ohne Kontrolle gestartet",
|
TriggerDE: "Werkzeug liegt im Arbeitsraum, Maschine wird ohne Kontrolle gestartet",
|
||||||
HarmDE: "Wegschleudern des Werkzeugs, schwere Verletzungen durch Projektil",
|
HarmDE: "Wegschleudern des Werkzeugs, schwere Verletzungen durch Projektil",
|
||||||
AffectedDE: "Bedienpersonal, Personen im Umfeld",
|
AffectedDE: "Bedienpersonal, Personen im Umfeld",
|
||||||
@@ -262,7 +262,7 @@ func GetExtendedHazardPatterns2() []HazardPattern {
|
|||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M003", "M005"},
|
SuggestedMeasureIDs: []string{"M003", "M005"},
|
||||||
SuggestedEvidenceIDs: []string{"E08"},
|
SuggestedEvidenceIDs: []string{"E08"},
|
||||||
Priority: 80,
|
Priority: 80, MachineTypes: []string{"press"},
|
||||||
ScenarioDE: "Exzentrische Belastung des Stoessels fuehrt zu seitlichem Ausbrechen des Werkstuecks.",
|
ScenarioDE: "Exzentrische Belastung des Stoessels fuehrt zu seitlichem Ausbrechen des Werkstuecks.",
|
||||||
TriggerDE: "Werkstueck nicht korrekt positioniert, seitliche Kraftkomponente entsteht",
|
TriggerDE: "Werkstueck nicht korrekt positioniert, seitliche Kraftkomponente entsteht",
|
||||||
HarmDE: "Aufprallverletzung durch geschleudertes Werkstueck, Quetschung",
|
HarmDE: "Aufprallverletzung durch geschleudertes Werkstueck, Quetschung",
|
||||||
@@ -290,7 +290,7 @@ func GetExtendedHazardPatterns2() []HazardPattern {
|
|||||||
// Roboter/Cobot erweitert (HP151-HP154)
|
// Roboter/Cobot erweitert (HP151-HP154)
|
||||||
// ================================================================
|
// ================================================================
|
||||||
{
|
{
|
||||||
ID: "HP151", NameDE: "Kollision bei Teach-In-Betrieb", NameEN: "Collision during teach-in operation",
|
ID: "HP151", NameDE: "Kollision im manuellen Verfahrbetrieb", NameEN: "Collision during teach-in operation",
|
||||||
RequiredComponentTags: []string{"programmable", "moving_part"},
|
RequiredComponentTags: []string{"programmable", "moving_part"},
|
||||||
RequiredEnergyTags: []string{},
|
RequiredEnergyTags: []string{},
|
||||||
RequiredLifecycles: []string{"setup"},
|
RequiredLifecycles: []string{"setup"},
|
||||||
@@ -336,7 +336,7 @@ func GetExtendedHazardPatterns2() []HazardPattern {
|
|||||||
DefaultSeverity: 3, DefaultExposure: 2,
|
DefaultSeverity: 3, DefaultExposure: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP154", NameDE: "Kollision zweier Roboter", NameEN: "Collision of two robots",
|
ID: "HP154", MachineTypes: []string{"robotics_cobot"}, NameDE: "Kollision zweier Roboter", NameEN: "Collision of two robots",
|
||||||
RequiredComponentTags: []string{"programmable", "moving_part"},
|
RequiredComponentTags: []string{"programmable", "moving_part"},
|
||||||
RequiredEnergyTags: []string{"kinetic"},
|
RequiredEnergyTags: []string{"kinetic"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
@@ -361,7 +361,7 @@ func GetExtendedHazardPatterns2() []HazardPattern {
|
|||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M001", "M051"},
|
SuggestedMeasureIDs: []string{"M001", "M051"},
|
||||||
SuggestedEvidenceIDs: []string{"E08", "E20"},
|
SuggestedEvidenceIDs: []string{"E08", "E20"},
|
||||||
Priority: 80,
|
Priority: 80, MachineTypes: []string{"conveyor", "packaging"},
|
||||||
ScenarioDE: "Finger oder Kleidung werden an der Bandumlenkstelle eingezogen.",
|
ScenarioDE: "Finger oder Kleidung werden an der Bandumlenkstelle eingezogen.",
|
||||||
TriggerDE: "Eingriff am laufenden Band, lose Kleidung geraet in Umlenkrolle",
|
TriggerDE: "Eingriff am laufenden Band, lose Kleidung geraet in Umlenkrolle",
|
||||||
HarmDE: "Fingeramputation, Armverletzung, Strangulation durch eingezogene Kleidung",
|
HarmDE: "Fingeramputation, Armverletzung, Strangulation durch eingezogene Kleidung",
|
||||||
@@ -595,7 +595,7 @@ func GetExtendedHazardPatterns2() []HazardPattern {
|
|||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M003", "M051"},
|
SuggestedMeasureIDs: []string{"M003", "M051"},
|
||||||
SuggestedEvidenceIDs: []string{"E08", "E20"},
|
SuggestedEvidenceIDs: []string{"E08", "E20"},
|
||||||
Priority: 80,
|
Priority: 80, MachineTypes: []string{"rotary_transfer"},
|
||||||
ScenarioDE: "Hand wird zwischen Drehteller und festem Anschlag eingeklemmt bei Taktbewegung.",
|
ScenarioDE: "Hand wird zwischen Drehteller und festem Anschlag eingeklemmt bei Taktbewegung.",
|
||||||
TriggerDE: "Eingriff waehrend der Taktbewegung, fehlende Schutzabdeckung am Drehteller",
|
TriggerDE: "Eingriff waehrend der Taktbewegung, fehlende Schutzabdeckung am Drehteller",
|
||||||
HarmDE: "Quetschung, Fingerfraktur, Amputation von Fingern",
|
HarmDE: "Quetschung, Fingerfraktur, Amputation von Fingern",
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func GetDGUVExtendedPatterns() []HazardPattern {
|
|||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M001"},
|
SuggestedMeasureIDs: []string{"M001"},
|
||||||
Priority: 60,
|
Priority: 60,
|
||||||
ScenarioDE: "Reibung an rotierender Welle oder Walze bei Wartung", HarmDE: "Hautabschuerfungen, Verbrennungen durch Reibungswaerme",
|
ScenarioDE: "Reibung an rotierender Welle oder Walze", HarmDE: "Hautabschuerfungen, Verbrennungen durch Reibungswaerme",
|
||||||
TriggerDE: "Beruehrung laufender Teile", AffectedDE: "Wartungspersonal", ZoneDE: "Walzen-/Wellenbereich", DefaultSeverity: 2, DefaultExposure: 3,
|
TriggerDE: "Beruehrung laufender Teile", AffectedDE: "Wartungspersonal", ZoneDE: "Walzen-/Wellenbereich", DefaultSeverity: 2, DefaultExposure: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -102,7 +102,7 @@ func GetDGUVExtendedPatterns() []HazardPattern {
|
|||||||
RequiredEnergyTags: []string{},
|
RequiredEnergyTags: []string{},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M051"},
|
SuggestedMeasureIDs: []string{"M051"},
|
||||||
Priority: 80,
|
Priority: 80, MachineTypes: []string{"crane", "construction"},
|
||||||
ScenarioDE: "Unkontrolliertes Schwingen einer angehobenen Last", HarmDE: "Quetschung, Erschlagen durch pendelnde Last",
|
ScenarioDE: "Unkontrolliertes Schwingen einer angehobenen Last", HarmDE: "Quetschung, Erschlagen durch pendelnde Last",
|
||||||
TriggerDE: "Schraeger Zug oder ploetzliches Abstoppen", AffectedDE: "Kranfuehrer, Anschlaeger", ZoneDE: "Schwenkbereich des Krans", DefaultSeverity: 4, DefaultExposure: 3,
|
TriggerDE: "Schraeger Zug oder ploetzliches Abstoppen", AffectedDE: "Kranfuehrer, Anschlaeger", ZoneDE: "Schwenkbereich des Krans", DefaultSeverity: 4, DefaultExposure: 3,
|
||||||
},
|
},
|
||||||
@@ -261,13 +261,13 @@ func GetDGUVExtendedPatterns() []HazardPattern {
|
|||||||
TriggerDE: "Hautkontakt mit kontaminiertem Fluid", AffectedDE: "Maschinenbediener, Wartungspersonal", ZoneDE: "Fluidsystem, Tank", DefaultSeverity: 2, DefaultExposure: 3,
|
TriggerDE: "Hautkontakt mit kontaminiertem Fluid", AffectedDE: "Maschinenbediener, Wartungspersonal", ZoneDE: "Fluidsystem, Tank", DefaultSeverity: 2, DefaultExposure: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP117", NameDE: "Asbest-/Mineralfaserfreisetzung bei Demontage", NameEN: "Asbestos/mineral fiber release during dismantling",
|
ID: "HP117", NameDE: "Asbest-/Mineralfaserfreisetzung", NameEN: "Asbestos/mineral fiber release during dismantling",
|
||||||
RequiredComponentTags: []string{"chemical_risk"},
|
RequiredComponentTags: []string{"chemical_risk"},
|
||||||
RequiredLifecycles: []string{"decommissioning", "disposal"},
|
RequiredLifecycles: []string{"decommissioning", "disposal"},
|
||||||
GeneratedHazardCats: []string{"material_environmental"},
|
GeneratedHazardCats: []string{"material_environmental"},
|
||||||
SuggestedMeasureIDs: []string{"M141"},
|
SuggestedMeasureIDs: []string{"M141"},
|
||||||
Priority: 90,
|
Priority: 90,
|
||||||
ScenarioDE: "Freisetzung von Asbestfasern bei Demontage alter Anlagen", HarmDE: "Asbestose, Mesotheliom (Langzeitfolge)",
|
ScenarioDE: "Freisetzung von Asbestfasern alter Anlagen", HarmDE: "Asbestose, Mesotheliom (Langzeitfolge)",
|
||||||
TriggerDE: "Mechanische Bearbeitung asbesthaltiger Bauteile", AffectedDE: "Demontagepersonal", ZoneDE: "Altanlagen, Isolierung", DefaultSeverity: 5, DefaultExposure: 1,
|
TriggerDE: "Mechanische Bearbeitung asbesthaltiger Bauteile", AffectedDE: "Demontagepersonal", ZoneDE: "Altanlagen, Isolierung", DefaultSeverity: 5, DefaultExposure: 1,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -428,7 +428,7 @@ func GetFinalPatternsA() []HazardPattern {
|
|||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M001", "M005"},
|
SuggestedMeasureIDs: []string{"M001", "M005"},
|
||||||
SuggestedEvidenceIDs: []string{"E01", "E08"},
|
SuggestedEvidenceIDs: []string{"E01", "E08"},
|
||||||
Priority: 78, ScenarioDE: "Finger wird zwischen Kette und Kettenrad eingezogen",
|
Priority: 78, MachineTypes: []string{"conveyor", "forestry"}, ScenarioDE: "Finger wird zwischen Kette und Kettenrad eingezogen",
|
||||||
TriggerDE: "Eingriff in ungeschuetzten Kettenantrieb", HarmDE: "Fingerquetschung, Abriss",
|
TriggerDE: "Eingriff in ungeschuetzten Kettenantrieb", HarmDE: "Fingerquetschung, Abriss",
|
||||||
AffectedDE: "Wartungspersonal", ZoneDE: "Kettenrad, Kettenstrang",
|
AffectedDE: "Wartungspersonal", ZoneDE: "Kettenrad, Kettenstrang",
|
||||||
DefaultSeverity: 4, DefaultExposure: 2,
|
DefaultSeverity: 4, DefaultExposure: 2,
|
||||||
@@ -667,13 +667,13 @@ func GetFinalPatternsA() []HazardPattern {
|
|||||||
DefaultSeverity: 5, DefaultExposure: 2,
|
DefaultSeverity: 5, DefaultExposure: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP1054", NameDE: "Herabfallendes Bauteil bei Montage", NameEN: "Falling component during assembly",
|
ID: "HP1054", NameDE: "Herabfallendes Bauteil", NameEN: "Falling component during assembly",
|
||||||
RequiredComponentTags: []string{"gravity_risk", "structural_part"},
|
RequiredComponentTags: []string{"gravity_risk", "structural_part"},
|
||||||
RequiredEnergyTags: []string{"gravitational"},
|
RequiredEnergyTags: []string{"gravitational"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M001", "M141"},
|
SuggestedMeasureIDs: []string{"M001", "M141"},
|
||||||
SuggestedEvidenceIDs: []string{"E01"},
|
SuggestedEvidenceIDs: []string{"E01"},
|
||||||
Priority: 68, ScenarioDE: "Bauteil loest sich bei Montage und faellt",
|
Priority: 68, ScenarioDE: "Bauteil loest sich und faellt",
|
||||||
TriggerDE: "Unzureichende Sicherung waehrend Zusammenbau", HarmDE: "Prellung, Fraktur",
|
TriggerDE: "Unzureichende Sicherung waehrend Zusammenbau", HarmDE: "Prellung, Fraktur",
|
||||||
AffectedDE: "Montagepersonal", ZoneDE: "Montageplatz, Regalbereich",
|
AffectedDE: "Montagepersonal", ZoneDE: "Montageplatz, Regalbereich",
|
||||||
DefaultSeverity: 3, DefaultExposure: 3,
|
DefaultSeverity: 3, DefaultExposure: 3,
|
||||||
@@ -814,7 +814,7 @@ func GetFinalPatternsA() []HazardPattern {
|
|||||||
},
|
},
|
||||||
// === Einklemmen Haare/Kleidung (3) ===
|
// === Einklemmen Haare/Kleidung (3) ===
|
||||||
{
|
{
|
||||||
ID: "HP1066", NameDE: "Haareinzug Drehmaschine", NameEN: "Hair entanglement lathe",
|
ID: "HP1066", MachineTypes: []string{"lathe", "cnc", "metalworking"}, NameDE: "Haareinzug Drehmaschine", NameEN: "Hair entanglement lathe",
|
||||||
RequiredComponentTags: []string{"rotating_part", "entanglement_risk"},
|
RequiredComponentTags: []string{"rotating_part", "entanglement_risk"},
|
||||||
RequiredEnergyTags: []string{"rotational"},
|
RequiredEnergyTags: []string{"rotational"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
@@ -1027,7 +1027,7 @@ func GetFinalPatternsA() []HazardPattern {
|
|||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M001", "M005"},
|
SuggestedMeasureIDs: []string{"M001", "M005"},
|
||||||
SuggestedEvidenceIDs: []string{"E01", "E08"},
|
SuggestedEvidenceIDs: []string{"E01", "E08"},
|
||||||
Priority: 78, ScenarioDE: "Schwere Maschine kippt bei Transport oder Betrieb",
|
Priority: 78, ScenarioDE: "Schwere Maschine kippt oder Betrieb",
|
||||||
TriggerDE: "Unebener Boden, Schwerpunktverlagerung", HarmDE: "Toedliche Quetschung",
|
TriggerDE: "Unebener Boden, Schwerpunktverlagerung", HarmDE: "Toedliche Quetschung",
|
||||||
AffectedDE: "Transportpersonal", ZoneDE: "Kippbereich, Aufstellflaeche",
|
AffectedDE: "Transportpersonal", ZoneDE: "Kippbereich, Aufstellflaeche",
|
||||||
DefaultSeverity: 5, DefaultExposure: 1,
|
DefaultSeverity: 5, DefaultExposure: 1,
|
||||||
|
|||||||
@@ -624,7 +624,7 @@ func GetFinalPatternsB() []HazardPattern {
|
|||||||
GeneratedHazardCats: []string{"material_environmental"},
|
GeneratedHazardCats: []string{"material_environmental"},
|
||||||
SuggestedMeasureIDs: []string{"M124", "M141"},
|
SuggestedMeasureIDs: []string{"M124", "M141"},
|
||||||
SuggestedEvidenceIDs: []string{"E20"},
|
SuggestedEvidenceIDs: []string{"E20"},
|
||||||
Priority: 82, ScenarioDE: "Asbestfasern werden bei Demontage/Wartung freigesetzt",
|
Priority: 82, ScenarioDE: "Asbestfasern werden /Wartung freigesetzt",
|
||||||
TriggerDE: "Bohren/Saegen in Asbestmaterial, Abrissarbeiten", HarmDE: "Asbestose, Mesotheliom",
|
TriggerDE: "Bohren/Saegen in Asbestmaterial, Abrissarbeiten", HarmDE: "Asbestose, Mesotheliom",
|
||||||
AffectedDE: "Wartungspersonal, Abbrucharbeiter", ZoneDE: "Altanlage, Dichtungen, Isolierungen",
|
AffectedDE: "Wartungspersonal, Abbrucharbeiter", ZoneDE: "Altanlage, Dichtungen, Isolierungen",
|
||||||
DefaultSeverity: 5, DefaultExposure: 1,
|
DefaultSeverity: 5, DefaultExposure: 1,
|
||||||
|
|||||||
@@ -860,7 +860,7 @@ func GetFinalPatternsC() []HazardPattern {
|
|||||||
GeneratedHazardCats: []string{"ergonomic_hazard"},
|
GeneratedHazardCats: []string{"ergonomic_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M141"},
|
SuggestedMeasureIDs: []string{"M141"},
|
||||||
SuggestedEvidenceIDs: []string{"E01"},
|
SuggestedEvidenceIDs: []string{"E01"},
|
||||||
Priority: 52, ScenarioDE: "Haeufiges Knien bei Montage/Wartungsarbeiten",
|
Priority: 52, ScenarioDE: "Haeufiges Knien /Wartungsarbeiten",
|
||||||
TriggerDE: "Bodennahe Arbeiten, fehlende Knieschoner", HarmDE: "Meniskusschaden (BK 2112)",
|
TriggerDE: "Bodennahe Arbeiten, fehlende Knieschoner", HarmDE: "Meniskusschaden (BK 2112)",
|
||||||
AffectedDE: "Wartungspersonal", ZoneDE: "Bodenbereich",
|
AffectedDE: "Wartungspersonal", ZoneDE: "Bodenbereich",
|
||||||
DefaultSeverity: 2, DefaultExposure: 4,
|
DefaultSeverity: 2, DefaultExposure: 4,
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ func GetFinalPatternsD() []HazardPattern {
|
|||||||
GeneratedHazardCats: []string{"mechanical_hazard", "maintenance_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard", "maintenance_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M001"},
|
SuggestedMeasureIDs: []string{"M001"},
|
||||||
SuggestedEvidenceIDs: []string{"E01"},
|
SuggestedEvidenceIDs: []string{"E01"},
|
||||||
Priority: 72, ScenarioDE: "Verschlissenes Teil versagt im Betrieb",
|
Priority: 72, ScenarioDE: "Verschlissenes Teil versagt",
|
||||||
TriggerDE: "Fehlende Inspektion, ueberschrittene Standzeit", HarmDE: "Funktionsverlust, Bruch",
|
TriggerDE: "Fehlende Inspektion, ueberschrittene Standzeit", HarmDE: "Funktionsverlust, Bruch",
|
||||||
AffectedDE: "Bedienpersonal", ZoneDE: "Verschleissteil, Fuehrung",
|
AffectedDE: "Bedienpersonal", ZoneDE: "Verschleissteil, Fuehrung",
|
||||||
DefaultSeverity: 3, DefaultExposure: 3,
|
DefaultSeverity: 3, DefaultExposure: 3,
|
||||||
@@ -573,7 +573,7 @@ func GetFinalPatternsD() []HazardPattern {
|
|||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M001", "M005"},
|
SuggestedMeasureIDs: []string{"M001", "M005"},
|
||||||
SuggestedEvidenceIDs: []string{"E01"},
|
SuggestedEvidenceIDs: []string{"E01"},
|
||||||
Priority: 72, ScenarioDE: "Schutzeinrichtung nach Einrichten nicht reaktiviert",
|
Priority: 72, ScenarioDE: "Schutzeinrichtung nicht reaktiviert",
|
||||||
TriggerDE: "Vergessen, Bypass noch aktiv", HarmDE: "Produktion ohne Schutz",
|
TriggerDE: "Vergessen, Bypass noch aktiv", HarmDE: "Produktion ohne Schutz",
|
||||||
AffectedDE: "Bedienpersonal", ZoneDE: "Gesamte Maschine",
|
AffectedDE: "Bedienpersonal", ZoneDE: "Gesamte Maschine",
|
||||||
DefaultSeverity: 4, DefaultExposure: 2,
|
DefaultSeverity: 4, DefaultExposure: 2,
|
||||||
@@ -817,7 +817,7 @@ func GetFinalPatternsD() []HazardPattern {
|
|||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M001", "M005"},
|
SuggestedMeasureIDs: []string{"M001", "M005"},
|
||||||
SuggestedEvidenceIDs: []string{"E01", "E08"},
|
SuggestedEvidenceIDs: []string{"E01", "E08"},
|
||||||
Priority: 78, ScenarioDE: "Kran schwenkt Last ueber besetzten Arbeitsplatz",
|
Priority: 78, MachineTypes: []string{"crane", "construction"}, ScenarioDE: "Kran schwenkt Last ueber besetzten Arbeitsplatz",
|
||||||
TriggerDE: "Fehlende Endschalter, Unachtsamkeit", HarmDE: "Herabfallende Last",
|
TriggerDE: "Fehlende Endschalter, Unachtsamkeit", HarmDE: "Herabfallende Last",
|
||||||
AffectedDE: "Personen darunter", ZoneDE: "Unter Kranschwenkbereich",
|
AffectedDE: "Personen darunter", ZoneDE: "Unter Kranschwenkbereich",
|
||||||
DefaultSeverity: 5, DefaultExposure: 2,
|
DefaultSeverity: 5, DefaultExposure: 2,
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ func GetFoodProcessingPatterns() []HazardPattern {
|
|||||||
RequiresExpertCalculation: true,
|
RequiresExpertCalculation: true,
|
||||||
ExpertHintDE: "IP-Schutzklasse muss fuer Nassreinigung (mindestens IPX5) nachgewiesen werden.",
|
ExpertHintDE: "IP-Schutzklasse muss fuer Nassreinigung (mindestens IPX5) nachgewiesen werden.",
|
||||||
ExpertHintEN: "IP rating must be verified for wet cleaning conditions (minimum IPX5).",
|
ExpertHintEN: "IP rating must be verified for wet cleaning conditions (minimum IPX5).",
|
||||||
ScenarioDE: "Wasser dringt beim Reinigen in elektrische Komponenten ein und erzeugt einen Fehlerstrom.",
|
ScenarioDE: "Wasser dringt in elektrische Komponenten ein und erzeugt einen Fehlerstrom.",
|
||||||
TriggerDE: "Unzureichende IP-Schutzklasse, defekte Kabeldurchfuehrungen, beschaedigtes Gehaeuse.",
|
TriggerDE: "Unzureichende IP-Schutzklasse, defekte Kabeldurchfuehrungen, beschaedigtes Gehaeuse.",
|
||||||
HarmDE: "Elektrischer Schlag, Herzkammerflimmern, Tod durch Stromschlag.",
|
HarmDE: "Elektrischer Schlag, Herzkammerflimmern, Tod durch Stromschlag.",
|
||||||
AffectedDE: "Reinigungspersonal, Bedienpersonal bei Nassreinigung.",
|
AffectedDE: "Reinigungspersonal, Bedienpersonal bei Nassreinigung.",
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func GetForestryConveyorPatterns() []HazardPattern {
|
|||||||
SuggestedMeasureIDs: []string{"M001", "M005"},
|
SuggestedMeasureIDs: []string{"M001", "M005"},
|
||||||
SuggestedEvidenceIDs: []string{"E08", "E20"},
|
SuggestedEvidenceIDs: []string{"E08", "E20"},
|
||||||
Priority: 85,
|
Priority: 85,
|
||||||
ScenarioDE: "Kontakt mit rotierendem Maehwerk bei Wartung oder durch Wegschleudern von Fremdkoerpern.",
|
ScenarioDE: "Kontakt mit rotierendem Maehwerk oder durch Wegschleudern von Fremdkoerpern.",
|
||||||
TriggerDE: "Wartung bei laufendem Maehwerk, fehlende Schutzabdeckung, Steinschleuder",
|
TriggerDE: "Wartung bei laufendem Maehwerk, fehlende Schutzabdeckung, Steinschleuder",
|
||||||
HarmDE: "Amputationsverletzung an Fuessen/Haenden, tiefe Schnittwunden, Augenverletzung durch Steinschlag",
|
HarmDE: "Amputationsverletzung an Fuessen/Haenden, tiefe Schnittwunden, Augenverletzung durch Steinschlag",
|
||||||
AffectedDE: "Maehwerksfahrer, Gartenarbeiter, Umstehende",
|
AffectedDE: "Maehwerksfahrer, Gartenarbeiter, Umstehende",
|
||||||
@@ -311,7 +311,7 @@ func GetForestryConveyorPatterns() []HazardPattern {
|
|||||||
SuggestedMeasureIDs: []string{"M052", "M141"},
|
SuggestedMeasureIDs: []string{"M052", "M141"},
|
||||||
SuggestedEvidenceIDs: []string{"E20"},
|
SuggestedEvidenceIDs: []string{"E20"},
|
||||||
Priority: 70,
|
Priority: 70,
|
||||||
ScenarioDE: "Person stuerzt von erhoehtem Rollenfoerderer bei Wartung oder Stoerungsbeseitigung.",
|
ScenarioDE: "Person stuerzt von erhoehtem Rollenfoerderer .",
|
||||||
TriggerDE: "Fehlende Absturzsicherung, kein Zugangsweg, improvisiertes Besteigen",
|
TriggerDE: "Fehlende Absturzsicherung, kein Zugangsweg, improvisiertes Besteigen",
|
||||||
HarmDE: "Knochenbrueche, Wirbelsaeulenverletzung, toedlicher Sturz ab 2 m Hoehe",
|
HarmDE: "Knochenbrueche, Wirbelsaeulenverletzung, toedlicher Sturz ab 2 m Hoehe",
|
||||||
AffectedDE: "Wartungspersonal, Bediener bei Stoerung",
|
AffectedDE: "Wartungspersonal, Bediener bei Stoerung",
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ func GetMaintenanceExtPatterns() []HazardPattern {
|
|||||||
RequiredComponentTags: []string{"moving_part"}, RequiredLifecycles: []string{"maintenance"},
|
RequiredComponentTags: []string{"moving_part"}, RequiredLifecycles: []string{"maintenance"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard", "pneumatic_hydraulic"},
|
GeneratedHazardCats: []string{"mechanical_hazard", "pneumatic_hydraulic"},
|
||||||
SuggestedMeasureIDs: []string{"M054", "M082"}, SuggestedEvidenceIDs: []string{"E08"}, Priority: 90,
|
SuggestedMeasureIDs: []string{"M054", "M082"}, SuggestedEvidenceIDs: []string{"E08"}, Priority: 90,
|
||||||
ScenarioDE: "Gespeicherte Energie entlaedt sich bei Wartung", TriggerDE: "Nicht abgelassener Druckspeicher",
|
ScenarioDE: "Gespeicherte Energie entlaedt sich", TriggerDE: "Nicht abgelassener Druckspeicher",
|
||||||
HarmDE: "Unkontrollierte Bewegung, Quetschung", AffectedDE: "Instandhalter", ZoneDE: "Antriebe, Speicher",
|
HarmDE: "Unkontrollierte Bewegung, Quetschung", AffectedDE: "Instandhalter", ZoneDE: "Antriebe, Speicher",
|
||||||
DefaultSeverity: 5, DefaultExposure: 3},
|
DefaultSeverity: 5, DefaultExposure: 3},
|
||||||
{ID: "HP702", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Falsches Werkzeug bei Wartung", NameEN: "Wrong tool during maintenance",
|
{ID: "HP702", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Falsches Werkzeug fuer Arbeiten an der Maschine", NameEN: "Wrong tool during maintenance",
|
||||||
RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"maintenance"},
|
RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"maintenance"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M082"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 50,
|
SuggestedMeasureIDs: []string{"M082"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 50,
|
||||||
@@ -33,11 +33,11 @@ func GetMaintenanceExtPatterns() []HazardPattern {
|
|||||||
ScenarioDE: "Unqualifiziertes Personal an Elektrik", TriggerDE: "Keine Elektrofachkraft",
|
ScenarioDE: "Unqualifiziertes Personal an Elektrik", TriggerDE: "Keine Elektrofachkraft",
|
||||||
HarmDE: "Stromschlag, Fehlverdrahtung", AffectedDE: "Instandhalter", ZoneDE: "Schaltschrank",
|
HarmDE: "Stromschlag, Fehlverdrahtung", AffectedDE: "Instandhalter", ZoneDE: "Schaltschrank",
|
||||||
DefaultSeverity: 4, DefaultExposure: 3},
|
DefaultSeverity: 4, DefaultExposure: 3},
|
||||||
{ID: "HP704", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Herabfallen schwerer Teile bei Demontage", NameEN: "Heavy parts falling during disassembly",
|
{ID: "HP704", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Herabfallen schwerer Teile", NameEN: "Heavy parts falling during disassembly",
|
||||||
RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"maintenance"},
|
RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"maintenance"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M082", "M141"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 75,
|
SuggestedMeasureIDs: []string{"M082", "M141"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 75,
|
||||||
ScenarioDE: "Schwere Teile fallen bei Demontage herab", TriggerDE: "Fehlende Abstuetzung",
|
ScenarioDE: "Schwere Teile fallen herab", TriggerDE: "Fehlende Abstuetzung",
|
||||||
HarmDE: "Quetschung, Frakturen, Tod", AffectedDE: "Instandhalter", ZoneDE: "Wartungsbereich",
|
HarmDE: "Quetschung, Frakturen, Tod", AffectedDE: "Instandhalter", ZoneDE: "Wartungsbereich",
|
||||||
DefaultSeverity: 5, DefaultExposure: 3},
|
DefaultSeverity: 5, DefaultExposure: 3},
|
||||||
{ID: "HP705", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Vergessenes Werkzeug in Maschine", NameEN: "Forgotten tool in machine",
|
{ID: "HP705", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Vergessenes Werkzeug in Maschine", NameEN: "Forgotten tool in machine",
|
||||||
@@ -54,7 +54,7 @@ func GetMaintenanceExtPatterns() []HazardPattern {
|
|||||||
ScenarioDE: "Scharfe Kanten und Grate verletzen", TriggerDE: "Fehlende Schutzhandschuhe",
|
ScenarioDE: "Scharfe Kanten und Grate verletzen", TriggerDE: "Fehlende Schutzhandschuhe",
|
||||||
HarmDE: "Schnittwunden, Abschuerfungen", AffectedDE: "Instandhalter", ZoneDE: "Blechverkleidungen",
|
HarmDE: "Schnittwunden, Abschuerfungen", AffectedDE: "Instandhalter", ZoneDE: "Blechverkleidungen",
|
||||||
DefaultSeverity: 2, DefaultExposure: 4},
|
DefaultSeverity: 2, DefaultExposure: 4},
|
||||||
{ID: "HP707", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Verbrennung an heissen Teilen bei Wartung", NameEN: "Burn on hot parts during maintenance",
|
{ID: "HP707", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Verbrennung an heissen Teilen", NameEN: "Burn on hot parts during maintenance",
|
||||||
RequiredComponentTags: []string{"high_temperature"}, RequiredLifecycles: []string{"maintenance"},
|
RequiredComponentTags: []string{"high_temperature"}, RequiredLifecycles: []string{"maintenance"},
|
||||||
GeneratedHazardCats: []string{"thermal_hazard"},
|
GeneratedHazardCats: []string{"thermal_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M005", "M082"}, SuggestedEvidenceIDs: []string{"E10"}, Priority: 60,
|
SuggestedMeasureIDs: []string{"M005", "M082"}, SuggestedEvidenceIDs: []string{"E10"}, Priority: 60,
|
||||||
@@ -72,11 +72,11 @@ func GetMaintenanceExtPatterns() []HazardPattern {
|
|||||||
RequiredComponentTags: []string{"chemical_risk"}, RequiredLifecycles: []string{"maintenance"},
|
RequiredComponentTags: []string{"chemical_risk"}, RequiredLifecycles: []string{"maintenance"},
|
||||||
GeneratedHazardCats: []string{"material_environmental"},
|
GeneratedHazardCats: []string{"material_environmental"},
|
||||||
SuggestedMeasureIDs: []string{"M005", "M082"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 50,
|
SuggestedMeasureIDs: []string{"M005", "M082"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 50,
|
||||||
ScenarioDE: "Verkeimter Kuehlschmierstoff bei Wartung", TriggerDE: "Altes KSS, Biofilme",
|
ScenarioDE: "Verkeimter Kuehlschmierstoff", TriggerDE: "Altes KSS, Biofilme",
|
||||||
HarmDE: "Hautinfektionen, Atemwegsbeschwerden", AffectedDE: "Instandhalter", ZoneDE: "KSS-System",
|
HarmDE: "Hautinfektionen, Atemwegsbeschwerden", AffectedDE: "Instandhalter", ZoneDE: "KSS-System",
|
||||||
DefaultSeverity: 2, DefaultExposure: 3},
|
DefaultSeverity: 2, DefaultExposure: 3},
|
||||||
// — Einrichten / Umruesten (HP710-HP719) —
|
// — Einrichten / Umruesten (HP710-HP719) —
|
||||||
{ID: "HP710", OperationalStates: []string{"teach_mode"}, HumanRoles: []string{"programmer"}, NameDE: "Falsche Parameter nach Umruestung", NameEN: "Wrong parameters after changeover",
|
{ID: "HP710", OperationalStates: []string{"teach_mode"}, HumanRoles: []string{"programmer"}, NameDE: "Falsche Parameter nach Produktwechsel", NameEN: "Wrong parameters after changeover",
|
||||||
RequiredComponentTags: []string{"programmable"}, RequiredLifecycles: []string{"setup"},
|
RequiredComponentTags: []string{"programmable"}, RequiredLifecycles: []string{"setup"},
|
||||||
GeneratedHazardCats: []string{"safety_function_failure"},
|
GeneratedHazardCats: []string{"safety_function_failure"},
|
||||||
SuggestedMeasureIDs: []string{"M106", "M082"}, SuggestedEvidenceIDs: []string{"E08"}, Priority: 75,
|
SuggestedMeasureIDs: []string{"M106", "M082"}, SuggestedEvidenceIDs: []string{"E08"}, Priority: 75,
|
||||||
@@ -90,7 +90,7 @@ func GetMaintenanceExtPatterns() []HazardPattern {
|
|||||||
ScenarioDE: "Schwere Werkzeuge manuell gewechselt", TriggerDE: "Kein Hebezeug, Finger eingeklemmt",
|
ScenarioDE: "Schwere Werkzeuge manuell gewechselt", TriggerDE: "Kein Hebezeug, Finger eingeklemmt",
|
||||||
HarmDE: "Quetschung, Amputation", AffectedDE: "Einrichter", ZoneDE: "Werkzeugaufnahme",
|
HarmDE: "Quetschung, Amputation", AffectedDE: "Einrichter", ZoneDE: "Werkzeugaufnahme",
|
||||||
DefaultSeverity: 4, DefaultExposure: 4},
|
DefaultSeverity: 4, DefaultExposure: 4},
|
||||||
{ID: "HP712", OperationalStates: []string{"teach_mode", "manual_operation"}, HumanRoles: []string{"programmer", "maintenance_tech"}, NameDE: "Unkontrollierte Bewegung bei Testlauf", NameEN: "Uncontrolled movement test run",
|
{ID: "HP712", OperationalStates: []string{"teach_mode", "manual_operation"}, HumanRoles: []string{"programmer", "maintenance_tech"}, NameDE: "Unkontrollierte Bewegung nach Probelauf", NameEN: "Uncontrolled movement test run",
|
||||||
RequiredComponentTags: []string{"moving_part", "programmable"}, RequiredLifecycles: []string{"setup"},
|
RequiredComponentTags: []string{"moving_part", "programmable"}, RequiredLifecycles: []string{"setup"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M106", "M054"}, SuggestedEvidenceIDs: []string{"E08"}, Priority: 80,
|
SuggestedMeasureIDs: []string{"M106", "M054"}, SuggestedEvidenceIDs: []string{"E08"}, Priority: 80,
|
||||||
@@ -129,17 +129,17 @@ func GetMaintenanceExtPatterns() []HazardPattern {
|
|||||||
RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"setup"},
|
RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"setup"},
|
||||||
GeneratedHazardCats: []string{"material_environmental"},
|
GeneratedHazardCats: []string{"material_environmental"},
|
||||||
SuggestedMeasureIDs: []string{"M082"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 50,
|
SuggestedMeasureIDs: []string{"M082"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 50,
|
||||||
ScenarioDE: "Falsches Material nach Umruestung", TriggerDE: "Verwechslung, fehlende Kennzeichnung",
|
ScenarioDE: "Falsches Material", TriggerDE: "Verwechslung, fehlende Kennzeichnung",
|
||||||
HarmDE: "Werkzeugbruch, Splitterflug", AffectedDE: "Bedienpersonal", ZoneDE: "Materialzufuhr",
|
HarmDE: "Werkzeugbruch, Splitterflug", AffectedDE: "Bedienpersonal", ZoneDE: "Materialzufuhr",
|
||||||
DefaultSeverity: 3, DefaultExposure: 3},
|
DefaultSeverity: 3, DefaultExposure: 3},
|
||||||
{ID: "HP718", NameDE: "Absturz bei Einrichtung hoher Maschine", NameEN: "Fall during tall machine setup",
|
{ID: "HP718", NameDE: "Absturz hoher Maschine", NameEN: "Fall during tall machine setup",
|
||||||
RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"setup"},
|
RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"setup"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M082", "M141"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 65,
|
SuggestedMeasureIDs: []string{"M082", "M141"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 65,
|
||||||
ScenarioDE: "Einrichtarbeiten in Hoehe ohne sicheren Zugang", TriggerDE: "Improvisierte Aufstiegshilfe",
|
ScenarioDE: "Einrichtarbeiten in Hoehe ohne sicheren Zugang", TriggerDE: "Improvisierte Aufstiegshilfe",
|
||||||
HarmDE: "Absturz, Frakturen", AffectedDE: "Einrichter", ZoneDE: "Maschinenoberteil",
|
HarmDE: "Absturz, Frakturen", AffectedDE: "Einrichter", ZoneDE: "Maschinenoberteil",
|
||||||
DefaultSeverity: 4, DefaultExposure: 3},
|
DefaultSeverity: 4, DefaultExposure: 3},
|
||||||
{ID: "HP719", NameDE: "Schutzeinrichtung nach Umruestung defekt", NameEN: "Faulty guard after changeover",
|
{ID: "HP719", NameDE: "Schutzeinrichtung nach Produktwechsel defekt", NameEN: "Faulty guard after changeover",
|
||||||
RequiredComponentTags: []string{"moving_part"}, RequiredLifecycles: []string{"setup"},
|
RequiredComponentTags: []string{"moving_part"}, RequiredLifecycles: []string{"setup"},
|
||||||
GeneratedHazardCats: []string{"safety_function_failure", "mechanical_hazard"},
|
GeneratedHazardCats: []string{"safety_function_failure", "mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M082", "M141"}, SuggestedEvidenceIDs: []string{"E08"}, Priority: 80,
|
SuggestedMeasureIDs: []string{"M082", "M141"}, SuggestedEvidenceIDs: []string{"E08"}, Priority: 80,
|
||||||
@@ -218,7 +218,7 @@ func GetMaintenanceExtPatterns() []HazardPattern {
|
|||||||
HarmDE: "Folgestoerung mit groesserem Schaden", AffectedDE: "Bedienpersonal", ZoneDE: "Steuerung",
|
HarmDE: "Folgestoerung mit groesserem Schaden", AffectedDE: "Bedienpersonal", ZoneDE: "Steuerung",
|
||||||
DefaultSeverity: 4, DefaultExposure: 2},
|
DefaultSeverity: 4, DefaultExposure: 2},
|
||||||
// — Transport / Montage (HP900-HP907) —
|
// — Transport / Montage (HP900-HP907) —
|
||||||
{ID: "HP900", NameDE: "Kippen der Maschine beim Transport", NameEN: "Machine tipping during transport",
|
{ID: "HP900", NameDE: "Kippen der Maschine", NameEN: "Machine tipping during transport",
|
||||||
RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"transport"},
|
RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"transport"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M082", "M141"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 80,
|
SuggestedMeasureIDs: []string{"M082", "M141"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 80,
|
||||||
@@ -267,7 +267,7 @@ func GetMaintenanceExtPatterns() []HazardPattern {
|
|||||||
ScenarioDE: "Stapler kollidiert mit Personen", TriggerDE: "Eingeschraenkte Sicht, zu schnell",
|
ScenarioDE: "Stapler kollidiert mit Personen", TriggerDE: "Eingeschraenkte Sicht, zu schnell",
|
||||||
HarmDE: "Anfahrunfall, Quetschung", AffectedDE: "Fussgaenger", ZoneDE: "Transportwege",
|
HarmDE: "Anfahrunfall, Quetschung", AffectedDE: "Fussgaenger", ZoneDE: "Transportwege",
|
||||||
DefaultSeverity: 4, DefaultExposure: 3},
|
DefaultSeverity: 4, DefaultExposure: 3},
|
||||||
{ID: "HP907", NameDE: "Verankerungsfehler bei Montage", NameEN: "Anchoring error installation",
|
{ID: "HP907", NameDE: "Verankerungsfehler am Aufstellort", NameEN: "Anchoring error installation",
|
||||||
RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"transport"},
|
RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"transport"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M082"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 65,
|
SuggestedMeasureIDs: []string{"M082"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 65,
|
||||||
@@ -339,7 +339,7 @@ func GetMaintenanceExtPatterns() []HazardPattern {
|
|||||||
ScenarioDE: "Reinigung ohne Abschaltung der Maschine", TriggerDE: "Zeitdruck",
|
ScenarioDE: "Reinigung ohne Abschaltung der Maschine", TriggerDE: "Zeitdruck",
|
||||||
HarmDE: "Einzug, Quetschung, Aufwickeln", AffectedDE: "Reinigungspersonal", ZoneDE: "Rotierende Teile",
|
HarmDE: "Einzug, Quetschung, Aufwickeln", AffectedDE: "Reinigungspersonal", ZoneDE: "Rotierende Teile",
|
||||||
DefaultSeverity: 5, DefaultExposure: 3},
|
DefaultSeverity: 5, DefaultExposure: 3},
|
||||||
{ID: "HP917", NameDE: "Nassrutschiger Boden nach Reinigung", NameEN: "Wet slippery floor after cleaning",
|
{ID: "HP917", NameDE: "Nassrutschiger Boden durch Fluessigkeiten", NameEN: "Wet slippery floor after cleaning",
|
||||||
RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"cleaning"},
|
RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"cleaning"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M082"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 45,
|
SuggestedMeasureIDs: []string{"M082"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 45,
|
||||||
@@ -410,7 +410,7 @@ func GetMaintenanceExtPatterns() []HazardPattern {
|
|||||||
RequiredComponentTags: []string{"electrical_part"}, RequiredEnergyTags: []string{"electrical"},
|
RequiredComponentTags: []string{"electrical_part"}, RequiredEnergyTags: []string{"electrical"},
|
||||||
RequiredLifecycles: []string{"maintenance"}, GeneratedHazardCats: []string{"electrical_hazard"},
|
RequiredLifecycles: []string{"maintenance"}, GeneratedHazardCats: []string{"electrical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M082", "M141"}, SuggestedEvidenceIDs: []string{"E09"}, Priority: 75,
|
SuggestedMeasureIDs: []string{"M082", "M141"}, SuggestedEvidenceIDs: []string{"E09"}, Priority: 75,
|
||||||
ScenarioDE: "Messung unter Spannung bei Fehlersuche", TriggerDE: "Messgeraet rutscht ab",
|
ScenarioDE: "Messung unter Spannung", TriggerDE: "Messgeraet rutscht ab",
|
||||||
HarmDE: "Stromschlag, Lichtbogen", AffectedDE: "Elektrofachkraft", ZoneDE: "Schaltschrank",
|
HarmDE: "Stromschlag, Lichtbogen", AffectedDE: "Elektrofachkraft", ZoneDE: "Schaltschrank",
|
||||||
DefaultSeverity: 4, DefaultExposure: 3},
|
DefaultSeverity: 4, DefaultExposure: 3},
|
||||||
{ID: "HP927", NameDE: "ZfP mit Strahlenquelle", NameEN: "NDT with radiation source",
|
{ID: "HP927", NameDE: "ZfP mit Strahlenquelle", NameEN: "NDT with radiation source",
|
||||||
@@ -451,7 +451,7 @@ func GetMaintenanceExtPatterns() []HazardPattern {
|
|||||||
HarmDE: "Vernachlaessigte Sicherheit", AffectedDE: "Alle Gewerke", ZoneDE: "Schnittstellen",
|
HarmDE: "Vernachlaessigte Sicherheit", AffectedDE: "Alle Gewerke", ZoneDE: "Schnittstellen",
|
||||||
DefaultSeverity: 3, DefaultExposure: 3},
|
DefaultSeverity: 3, DefaultExposure: 3},
|
||||||
// — Notfall (HP932-HP934) —
|
// — Notfall (HP932-HP934) —
|
||||||
{ID: "HP932", NameDE: "Versperrte Fluchtwege bei Wartung", NameEN: "Blocked escape routes maintenance",
|
{ID: "HP932", NameDE: "Versperrte Fluchtwege durch abgestelltes Material", NameEN: "Blocked escape routes maintenance",
|
||||||
RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"maintenance"},
|
RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"maintenance"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M082"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 70,
|
SuggestedMeasureIDs: []string{"M082"}, SuggestedEvidenceIDs: []string{"E20"}, Priority: 70,
|
||||||
@@ -465,11 +465,11 @@ func GetMaintenanceExtPatterns() []HazardPattern {
|
|||||||
ScenarioDE: "Kein Erste-Hilfe-Material am abgelegenen Ort", TriggerDE: "Entfernter Standort",
|
ScenarioDE: "Kein Erste-Hilfe-Material am abgelegenen Ort", TriggerDE: "Entfernter Standort",
|
||||||
HarmDE: "Verzoegerte Erstversorgung", AffectedDE: "Instandhalter", ZoneDE: "Abgelegene Wartungsorte",
|
HarmDE: "Verzoegerte Erstversorgung", AffectedDE: "Instandhalter", ZoneDE: "Abgelegene Wartungsorte",
|
||||||
DefaultSeverity: 3, DefaultExposure: 3},
|
DefaultSeverity: 3, DefaultExposure: 3},
|
||||||
{ID: "HP934", NameDE: "Brandbekaempfung bei Wartung", NameEN: "Firefighting during maintenance",
|
{ID: "HP934", NameDE: "Erschwerter Zugang zu Loescheinrichtungen", NameEN: "Firefighting during maintenance",
|
||||||
RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"maintenance"},
|
RequiredComponentTags: []string{"structural_part"}, RequiredLifecycles: []string{"maintenance"},
|
||||||
GeneratedHazardCats: []string{"thermal_hazard"},
|
GeneratedHazardCats: []string{"thermal_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M082", "M141"}, SuggestedEvidenceIDs: []string{"E10", "E20"}, Priority: 65,
|
SuggestedMeasureIDs: []string{"M082", "M141"}, SuggestedEvidenceIDs: []string{"E10", "E20"}, Priority: 65,
|
||||||
ScenarioDE: "Feuerloescher nicht erreichbar bei Wartung", TriggerDE: "Verstellter Loescher",
|
ScenarioDE: "Feuerloescher nicht erreichbar", TriggerDE: "Verstellter Loescher",
|
||||||
HarmDE: "Brandausbreitung, Verbrennungen", AffectedDE: "Instandhalter", ZoneDE: "Wartungsbereich",
|
HarmDE: "Brandausbreitung, Verbrennungen", AffectedDE: "Instandhalter", ZoneDE: "Wartungsbereich",
|
||||||
DefaultSeverity: 4, DefaultExposure: 2},
|
DefaultSeverity: 4, DefaultExposure: 2},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ func builtinMechanicalPatterns() []HazardPattern {
|
|||||||
TriggerDE: "Bediener befindet sich im Kraftwirkbereich waehrend des Arbeitshubes oder bei Stoerungsbeseitigung.",
|
TriggerDE: "Bediener befindet sich im Kraftwirkbereich waehrend des Arbeitshubes oder bei Stoerungsbeseitigung.",
|
||||||
HarmDE: "Schwere Quetschung, Fraktur, innere Verletzungen, Todesfolge bei Ganzkompression.",
|
HarmDE: "Schwere Quetschung, Fraktur, innere Verletzungen, Todesfolge bei Ganzkompression.",
|
||||||
AffectedDE: "Bedienpersonal, Einrichter, Wartungspersonal",
|
AffectedDE: "Bedienpersonal, Einrichter, Wartungspersonal",
|
||||||
ZoneDE: "Kraftwirkbereich (Pressenraum, Vorschubachse), Einlegestelle",
|
ZoneDE: "Kraftwirkbereich, Einlegestelle, Vorschubachse",
|
||||||
DefaultSeverity: 5, DefaultExposure: 3,
|
DefaultSeverity: 5, DefaultExposure: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -120,7 +120,7 @@ func builtinMechanicalPatterns() []HazardPattern {
|
|||||||
TriggerDE: "Versagen einer Halterung, Bruch eines Lastaufnahmemittels oder Abrutschen bei Wartungsarbeiten in der Hoehe.",
|
TriggerDE: "Versagen einer Halterung, Bruch eines Lastaufnahmemittels oder Abrutschen bei Wartungsarbeiten in der Hoehe.",
|
||||||
HarmDE: "Kopfverletzung, Fraktur, Quetschung durch herabfallende Last; Sturzverletung.",
|
HarmDE: "Kopfverletzung, Fraktur, Quetschung durch herabfallende Last; Sturzverletung.",
|
||||||
AffectedDE: "Wartungspersonal, Bedienpersonal, Personen im Gefahrenbereich",
|
AffectedDE: "Wartungspersonal, Bedienpersonal, Personen im Gefahrenbereich",
|
||||||
ZoneDE: "Bereich unterhalb angehobener Lasten, Wartungsplattformen, Kran-/Hebezeugbereich",
|
ZoneDE: "Bereich unterhalb angehobener Lasten, Wartungsplattformen",
|
||||||
DefaultSeverity: 4, DefaultExposure: 2,
|
DefaultSeverity: 4, DefaultExposure: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ func GetOperationalHazardPatterns() []HazardPattern {
|
|||||||
DefaultSeverity: 4, DefaultExposure: 2,
|
DefaultSeverity: 4, DefaultExposure: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP075", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Kontakt mit heissen Teilen bei Wartung", NameEN: "Contact with hot parts during maintenance",
|
ID: "HP075", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Kontakt mit heissen Oberflaechen", NameEN: "Contact with hot parts during maintenance",
|
||||||
RequiredComponentTags: []string{"high_temperature"},
|
RequiredComponentTags: []string{"high_temperature"},
|
||||||
RequiredLifecycles: []string{"maintenance"},
|
RequiredLifecycles: []string{"maintenance"},
|
||||||
GeneratedHazardCats: []string{"thermal_hazard"},
|
GeneratedHazardCats: []string{"thermal_hazard"},
|
||||||
@@ -165,7 +165,7 @@ func GetOperationalHazardPatterns() []HazardPattern {
|
|||||||
DefaultSeverity: 3, DefaultExposure: 3,
|
DefaultSeverity: 3, DefaultExposure: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP076", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Kontakt mit Gefahrstoffen bei Wartung", NameEN: "Contact with hazardous substances during maintenance",
|
ID: "HP076", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Kontakt mit Gefahrstoffen", NameEN: "Contact with hazardous substances during maintenance",
|
||||||
RequiredComponentTags: []string{"chemical_risk"},
|
RequiredComponentTags: []string{"chemical_risk"},
|
||||||
RequiredLifecycles: []string{"maintenance", "cleaning"},
|
RequiredLifecycles: []string{"maintenance", "cleaning"},
|
||||||
GeneratedHazardCats: []string{"material_environmental"},
|
GeneratedHazardCats: []string{"material_environmental"},
|
||||||
@@ -179,7 +179,7 @@ func GetOperationalHazardPatterns() []HazardPattern {
|
|||||||
DefaultSeverity: 3, DefaultExposure: 3,
|
DefaultSeverity: 3, DefaultExposure: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP077", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Elektrischer Schlag bei Wartungsarbeiten", NameEN: "Electric shock during maintenance",
|
ID: "HP077", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Elektrischer Schlag an offenen Baugruppen", NameEN: "Electric shock during maintenance",
|
||||||
RequiredComponentTags: []string{"high_voltage"},
|
RequiredComponentTags: []string{"high_voltage"},
|
||||||
RequiredLifecycles: []string{"maintenance", "fault_clearing"},
|
RequiredLifecycles: []string{"maintenance", "fault_clearing"},
|
||||||
GeneratedHazardCats: []string{"electrical_hazard"},
|
GeneratedHazardCats: []string{"electrical_hazard"},
|
||||||
@@ -195,7 +195,7 @@ func GetOperationalHazardPatterns() []HazardPattern {
|
|||||||
DefaultSeverity: 5, DefaultExposure: 3,
|
DefaultSeverity: 5, DefaultExposure: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP078", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Ergonomische Belastung bei Wartungszugang", NameEN: "Ergonomic strain at maintenance access",
|
ID: "HP078", OperationalStates: []string{"maintenance"}, HumanRoles: []string{"maintenance_tech"}, NameDE: "Ergonomische Belastung durch schwierigen Zugang", NameEN: "Ergonomic strain at maintenance access",
|
||||||
RequiredComponentTags: []string{"structural_part"},
|
RequiredComponentTags: []string{"structural_part"},
|
||||||
RequiredLifecycles: []string{"maintenance"},
|
RequiredLifecycles: []string{"maintenance"},
|
||||||
GeneratedHazardCats: []string{"ergonomic"},
|
GeneratedHazardCats: []string{"ergonomic"},
|
||||||
@@ -273,7 +273,7 @@ func GetOperationalHazardPatterns() []HazardPattern {
|
|||||||
DefaultSeverity: 4, DefaultExposure: 3,
|
DefaultSeverity: 4, DefaultExposure: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP083", NameDE: "Unbeabsichtigter Hub bei Einrichtbetrieb", NameEN: "Unintended stroke in setup mode",
|
ID: "HP083", NameDE: "Unbeabsichtigter Hub im manuellen Betrieb", NameEN: "Unintended stroke in setup mode",
|
||||||
RequiredComponentTags: []string{"moving_part", "crush_point"},
|
RequiredComponentTags: []string{"moving_part", "crush_point"},
|
||||||
RequiredLifecycles: []string{"setup"},
|
RequiredLifecycles: []string{"setup"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard", "safety_function_failure"},
|
GeneratedHazardCats: []string{"mechanical_hazard", "safety_function_failure"},
|
||||||
@@ -281,7 +281,7 @@ func GetOperationalHazardPatterns() []HazardPattern {
|
|||||||
Priority: 94,
|
Priority: 94,
|
||||||
RequiresExpertCalculation: true,
|
RequiresExpertCalculation: true,
|
||||||
ExpertHintDE: "Einrichtbetrieb nur mit reduzierter Geschwindigkeit und Zweihandschaltung.",
|
ExpertHintDE: "Einrichtbetrieb nur mit reduzierter Geschwindigkeit und Zweihandschaltung.",
|
||||||
ScenarioDE: "Einrichter befindet sich im Werkzeugraum waehrend Testlauf im Einrichtbetrieb",
|
ScenarioDE: "Person befindet sich im Werkzeugraum waehrend Testlauf",
|
||||||
TriggerDE: "Stossel fuehrt vollen Hub statt Tipphub aus wegen Softwarefehler oder Fehlbedienung",
|
TriggerDE: "Stossel fuehrt vollen Hub statt Tipphub aus wegen Softwarefehler oder Fehlbedienung",
|
||||||
HarmDE: "Toedliches Quetschen oder Amputation durch vollen Pressenhub bei Anwesenheit",
|
HarmDE: "Toedliches Quetschen oder Amputation durch vollen Pressenhub bei Anwesenheit",
|
||||||
AffectedDE: "Einrichter",
|
AffectedDE: "Einrichter",
|
||||||
@@ -289,7 +289,7 @@ func GetOperationalHazardPatterns() []HazardPattern {
|
|||||||
DefaultSeverity: 5, DefaultExposure: 3,
|
DefaultSeverity: 5, DefaultExposure: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP084", NameDE: "Falsche Parametereinstellung nach Umruestung", NameEN: "Wrong parameters after changeover",
|
ID: "HP084", NameDE: "Falsche Parametereinstellung nach Produktwechsel", NameEN: "Wrong parameters after changeover",
|
||||||
RequiredComponentTags: []string{"programmable"},
|
RequiredComponentTags: []string{"programmable"},
|
||||||
RequiredLifecycles: []string{"changeover", "setup"},
|
RequiredLifecycles: []string{"changeover", "setup"},
|
||||||
GeneratedHazardCats: []string{"safety_function_failure"},
|
GeneratedHazardCats: []string{"safety_function_failure"},
|
||||||
@@ -323,7 +323,7 @@ func GetOperationalHazardPatterns() []HazardPattern {
|
|||||||
// Transport / Montage / Demontage (HP086-HP090)
|
// Transport / Montage / Demontage (HP086-HP090)
|
||||||
// ================================================================
|
// ================================================================
|
||||||
{
|
{
|
||||||
ID: "HP086", NameDE: "Kippen der Maschine beim Transport", NameEN: "Machine tipping during transport",
|
ID: "HP086", NameDE: "Kippen der Maschine", NameEN: "Machine tipping during transport",
|
||||||
RequiredComponentTags: []string{"structural_part"},
|
RequiredComponentTags: []string{"structural_part"},
|
||||||
RequiredLifecycles: []string{"transport"},
|
RequiredLifecycles: []string{"transport"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
@@ -337,7 +337,7 @@ func GetOperationalHazardPatterns() []HazardPattern {
|
|||||||
DefaultSeverity: 5, DefaultExposure: 2,
|
DefaultSeverity: 5, DefaultExposure: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP087", NameDE: "Quetschen bei Montage/Aufstellung", NameEN: "Crushing during installation",
|
ID: "HP087", NameDE: "Quetschen/Aufstellung", NameEN: "Crushing during installation",
|
||||||
RequiredComponentTags: []string{"high_force", "gravity_risk"},
|
RequiredComponentTags: []string{"high_force", "gravity_risk"},
|
||||||
RequiredLifecycles: []string{"assembly"},
|
RequiredLifecycles: []string{"assembly"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
@@ -351,7 +351,7 @@ func GetOperationalHazardPatterns() []HazardPattern {
|
|||||||
DefaultSeverity: 4, DefaultExposure: 2,
|
DefaultSeverity: 4, DefaultExposure: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP088", NameDE: "Unkontrollierte Bewegung bei Inbetriebnahme", NameEN: "Uncontrolled movement during commissioning",
|
ID: "HP088", NameDE: "Unkontrollierte Bewegung beim Erststart", NameEN: "Uncontrolled movement during commissioning",
|
||||||
RequiredComponentTags: []string{"moving_part", "programmable"},
|
RequiredComponentTags: []string{"moving_part", "programmable"},
|
||||||
RequiredLifecycles: []string{"commissioning"},
|
RequiredLifecycles: []string{"commissioning"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
@@ -365,7 +365,7 @@ func GetOperationalHazardPatterns() []HazardPattern {
|
|||||||
DefaultSeverity: 4, DefaultExposure: 2,
|
DefaultSeverity: 4, DefaultExposure: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP089", NameDE: "Restmedien bei Demontage (Oel, Gas, Druck)", NameEN: "Residual media during dismantling",
|
ID: "HP089", NameDE: "Restmedien (Oel, Gas, Druck)", NameEN: "Residual media during dismantling",
|
||||||
RequiredComponentTags: []string{"hydraulic_part"},
|
RequiredComponentTags: []string{"hydraulic_part"},
|
||||||
RequiredLifecycles: []string{"decommissioning", "disposal"},
|
RequiredLifecycles: []string{"decommissioning", "disposal"},
|
||||||
GeneratedHazardCats: []string{"material_environmental", "pneumatic_hydraulic"},
|
GeneratedHazardCats: []string{"material_environmental", "pneumatic_hydraulic"},
|
||||||
@@ -379,7 +379,7 @@ func GetOperationalHazardPatterns() []HazardPattern {
|
|||||||
DefaultSeverity: 3, DefaultExposure: 2,
|
DefaultSeverity: 3, DefaultExposure: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP090", NameDE: "Scharfe Kanten bei Demontage", NameEN: "Sharp edges during dismantling",
|
ID: "HP090", NameDE: "Scharfe Kanten an demontierten Teilen", NameEN: "Sharp edges during dismantling",
|
||||||
RequiredComponentTags: []string{"cutting_part"},
|
RequiredComponentTags: []string{"cutting_part"},
|
||||||
RequiredLifecycles: []string{"decommissioning", "disposal"},
|
RequiredLifecycles: []string{"decommissioning", "disposal"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
@@ -411,7 +411,7 @@ func GetOperationalHazardPatterns() []HazardPattern {
|
|||||||
DefaultSeverity: 2, DefaultExposure: 4,
|
DefaultSeverity: 2, DefaultExposure: 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP092", NameDE: "Chemische Exposition bei Reinigung", NameEN: "Chemical exposure during cleaning",
|
ID: "HP092", NameDE: "Chemische Exposition durch Reinigungsmittel", NameEN: "Chemical exposure during cleaning",
|
||||||
RequiredComponentTags: []string{"chemical_risk"},
|
RequiredComponentTags: []string{"chemical_risk"},
|
||||||
RequiredLifecycles: []string{"cleaning"},
|
RequiredLifecycles: []string{"cleaning"},
|
||||||
GeneratedHazardCats: []string{"material_environmental"},
|
GeneratedHazardCats: []string{"material_environmental"},
|
||||||
@@ -425,7 +425,7 @@ func GetOperationalHazardPatterns() []HazardPattern {
|
|||||||
DefaultSeverity: 3, DefaultExposure: 3,
|
DefaultSeverity: 3, DefaultExposure: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP093", NameDE: "Einziehen in rotierende Teile bei Reinigung", NameEN: "Draw-in by rotating parts during cleaning",
|
ID: "HP093", NameDE: "Einziehen in rotierende Teile bei laufender Maschine", NameEN: "Draw-in by rotating parts during cleaning",
|
||||||
RequiredComponentTags: []string{"rotating_part"},
|
RequiredComponentTags: []string{"rotating_part"},
|
||||||
RequiredLifecycles: []string{"cleaning"},
|
RequiredLifecycles: []string{"cleaning"},
|
||||||
ExcludedComponentTags: []string{"interlocked"},
|
ExcludedComponentTags: []string{"interlocked"},
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ func GetPlasticsMetalPatterns() []HazardPattern {
|
|||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M003", "M004", "M082"},
|
SuggestedMeasureIDs: []string{"M003", "M004", "M082"},
|
||||||
SuggestedEvidenceIDs: []string{"E08", "E09"},
|
SuggestedEvidenceIDs: []string{"E08", "E09"},
|
||||||
Priority: 95,
|
Priority: 95, MachineTypes: []string{"lathe", "cnc", "metalworking"},
|
||||||
ScenarioDE: "Offene Haare, Krawatten, Aermel oder Handschuhe werden vom rotierenden Werkstueck oder Spannfutter erfasst.",
|
ScenarioDE: "Offene Haare, Krawatten, Aermel oder Handschuhe werden vom rotierenden Werkstueck oder Spannfutter erfasst.",
|
||||||
TriggerDE: "Tragen von Handschuhen an der Drehmaschine, offene Haare, lose Kleidung",
|
TriggerDE: "Tragen von Handschuhen an der Drehmaschine, offene Haare, lose Kleidung",
|
||||||
HarmDE: "Skalpierung, Armfraktur, Strangulation, toedliche Aufwickelverletzung",
|
HarmDE: "Skalpierung, Armfraktur, Strangulation, toedliche Aufwickelverletzung",
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ func GetPressHazardPatterns() []HazardPattern {
|
|||||||
SuggestedMeasureIDs: []string{"M051", "M131"},
|
SuggestedMeasureIDs: []string{"M051", "M131"},
|
||||||
SuggestedEvidenceIDs: []string{"E01", "E08"},
|
SuggestedEvidenceIDs: []string{"E01", "E08"},
|
||||||
Priority: 92,
|
Priority: 92,
|
||||||
ScenarioDE: "Hydraulikspeicher entlaedt sich schlagartig bei Wartungsarbeiten oder Leitungsbruch.",
|
ScenarioDE: "Hydraulikspeicher entlaedt sich schlagartig oder Leitungsbruch.",
|
||||||
TriggerDE: "Oeffnen einer Leitung ohne vorherige Druckentlastung, Berstversagen des Speichers.",
|
TriggerDE: "Oeffnen einer Leitung ohne vorherige Druckentlastung, Berstversagen des Speichers.",
|
||||||
HarmDE: "Schwere Schnittverletzungen durch Oelstrahl, Augenverletzungen, Verbrennungen.",
|
HarmDE: "Schwere Schnittverletzungen durch Oelstrahl, Augenverletzungen, Verbrennungen.",
|
||||||
AffectedDE: "Instandhaltungspersonal, Hydraulik-Fachkraefte.",
|
AffectedDE: "Instandhaltungspersonal, Hydraulik-Fachkraefte.",
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ package iace
|
|||||||
// GetRobotCellPatterns returns hazard patterns for industrial robot cells
|
// GetRobotCellPatterns returns hazard patterns for industrial robot cells
|
||||||
// (non-collaborative) with safety fence, conveyors, and CNC machine tools.
|
// (non-collaborative) with safety fence, conveyors, and CNC machine tools.
|
||||||
// Based on typical ISO 10218-2 risk assessment scope for integrated robot systems.
|
// Based on typical ISO 10218-2 risk assessment scope for integrated robot systems.
|
||||||
|
//
|
||||||
|
// FORMULIERUNGSREGEL: Gefährdung und Szenario NEUTRAL formulieren — keine
|
||||||
|
// Lebensphasen im Text. Lebensphasen stehen in ApplicableLifecycles.
|
||||||
// HP1600-HP1649
|
// HP1600-HP1649
|
||||||
func GetRobotCellPatterns() []HazardPattern {
|
func GetRobotCellPatterns() []HazardPattern {
|
||||||
return []HazardPattern{
|
return []HazardPattern{
|
||||||
@@ -14,33 +17,22 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
RequiredComponentTags: []string{"moving_part"},
|
RequiredComponentTags: []string{"moving_part"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M061", "M062", "M054"},
|
SuggestedMeasureIDs: []string{"M061", "M062", "M054"},
|
||||||
Priority: 95, MachineTypes: []string{"robotics_cobot", "automotive", "metalworking", "general_industry"},
|
Priority: 99, MachineTypes: []string{"robotics_cobot", "automotive", "metalworking", "general_industry"},
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "teach_mode", "cleaning", "maintenance", "fault_clearing", "changeover"},
|
||||||
ScenarioDE: "Person befindet sich im Bewegungsbereich des Roboterarms und wird zwischen Roboterarm und feststehenden Anlagenteilen eingeklemmt.",
|
ScenarioDE: "Person befindet sich im Bewegungsbereich des Roboterarms und wird zwischen Roboterarm und feststehenden Anlagenteilen eingeklemmt.",
|
||||||
TriggerDE: "Roboterarm bewegt sich waehrend Person im Gefahrenbereich steht (z.B. nach Betreten der Roboterzelle).",
|
TriggerDE: "Roboterarm bewegt sich waehrend Person im Gefahrenbereich steht.",
|
||||||
HarmDE: "Quetschungen, Knochenbrueche, innere Verletzungen durch Einklemmen von Koerperteilen.",
|
HarmDE: "Quetschungen, Knochenbrueche, innere Verletzungen durch Einklemmen von Koerperteilen.",
|
||||||
AffectedDE: "Bedienpersonal, Einrichter, Wartungspersonal",
|
AffectedDE: "Bedienpersonal, Einrichter, Wartungspersonal, Reinigungspersonal",
|
||||||
ZoneDE: "Roboterarm, feststehende Anlagenteile innerhalb der Roboterzelle",
|
ZoneDE: "Roboterarm, feststehende Anlagenteile innerhalb der Roboterzelle",
|
||||||
DefaultSeverity: 4, DefaultExposure: 3,
|
DefaultSeverity: 4, DefaultExposure: 3,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
ID: "HP1601", NameDE: "Quetschen bei Teach-Betrieb am Roboter", NameEN: "Crushing during robot teach mode",
|
|
||||||
RequiredComponentTags: []string{"moving_part"},
|
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
|
||||||
SuggestedMeasureIDs: []string{"M054", "M061"},
|
|
||||||
Priority: 94, MachineTypes: []string{"robotics_cobot", "automotive", "metalworking", "general_industry"},
|
|
||||||
ScenarioDE: "Einrichter befindet sich zum Teachen/Programmieren in der Roboterzelle. Roboterarm bewegt sich unerwartet oder mit zu hoher Geschwindigkeit.",
|
|
||||||
TriggerDE: "Teach-Modus laesst Bewegung zu, Einrichter steht im Schwenkbereich des Arms.",
|
|
||||||
HarmDE: "Quetschungen, Prellungen durch Kontakt mit bewegtem Roboterarm.",
|
|
||||||
AffectedDE: "Einrichter, Programmierer",
|
|
||||||
ZoneDE: "Roboterarm, Inneres der Roboterzelle",
|
|
||||||
DefaultSeverity: 3, DefaultExposure: 3,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
ID: "HP1602", NameDE: "Durchgreifen durch Schutzzaun zum Roboter", NameEN: "Reaching through safety fence to robot",
|
ID: "HP1602", NameDE: "Durchgreifen durch Schutzzaun zum Roboter", NameEN: "Reaching through safety fence to robot",
|
||||||
RequiredComponentTags: []string{"moving_part", "guard"},
|
RequiredComponentTags: []string{"moving_part", "guard"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M002", "M061"},
|
SuggestedMeasureIDs: []string{"M002", "M061"},
|
||||||
Priority: 93,
|
Priority: 98,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "maintenance", "fault_clearing"},
|
||||||
ScenarioDE: "Person greift ueber oder durch den Schutzzaun und erreicht den Bewegungsbereich des Roboterarms.",
|
ScenarioDE: "Person greift ueber oder durch den Schutzzaun und erreicht den Bewegungsbereich des Roboterarms.",
|
||||||
TriggerDE: "Unzureichender Sicherheitsabstand zwischen Schutzzaun-Oberkante und Roboter-Schwenkbereich.",
|
TriggerDE: "Unzureichender Sicherheitsabstand zwischen Schutzzaun-Oberkante und Roboter-Schwenkbereich.",
|
||||||
HarmDE: "Quetschung von Hand oder Arm zwischen Roboterarm und feststehenden Teilen.",
|
HarmDE: "Quetschung von Hand oder Arm zwischen Roboterarm und feststehenden Teilen.",
|
||||||
@@ -53,11 +45,12 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
RequiredComponentTags: []string{"moving_part", "guard"},
|
RequiredComponentTags: []string{"moving_part", "guard"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M061", "M054", "M141"},
|
SuggestedMeasureIDs: []string{"M061", "M054", "M141"},
|
||||||
Priority: 94,
|
Priority: 99,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "maintenance", "fault_clearing", "changeover"},
|
||||||
ScenarioDE: "Person befindet sich in der Roboterzelle, Schutztuer wird geschlossen und Roboter startet. Person kann den Gefahrenbereich nicht rechtzeitig verlassen.",
|
ScenarioDE: "Person befindet sich in der Roboterzelle, Schutztuer wird geschlossen und Roboter startet. Person kann den Gefahrenbereich nicht rechtzeitig verlassen.",
|
||||||
TriggerDE: "Schutztuer schliesst waehrend Person im Innenraum. Wiederanlauf des Roboters ohne Quittierung.",
|
TriggerDE: "Schutztuer schliesst waehrend Person im Innenraum. Wiederanlauf des Roboters ohne Quittierung.",
|
||||||
HarmDE: "Quetschungen, Stoss durch anlaufenden Roboter. Person kann sich nicht entziehen.",
|
HarmDE: "Quetschungen, Stoss durch anlaufenden Roboter.",
|
||||||
AffectedDE: "Wartungspersonal, Einrichter",
|
AffectedDE: "Wartungspersonal, Einrichter, Reinigungspersonal",
|
||||||
ZoneDE: "Inneres der Roboterzelle",
|
ZoneDE: "Inneres der Roboterzelle",
|
||||||
DefaultSeverity: 4, DefaultExposure: 2,
|
DefaultSeverity: 4, DefaultExposure: 2,
|
||||||
},
|
},
|
||||||
@@ -66,24 +59,40 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
RequiredComponentTags: []string{"moving_part", "guard"},
|
RequiredComponentTags: []string{"moving_part", "guard"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M061", "M002"},
|
SuggestedMeasureIDs: []string{"M061", "M002"},
|
||||||
Priority: 92,
|
Priority: 98,
|
||||||
ScenarioDE: "Roboterarm ueberschreitet den vorgesehenen Bewegungsbereich und trifft den Schutzzaun mit hoher Kraft.",
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "changeover", "fault_clearing"},
|
||||||
TriggerDE: "Fehler in der Bahnplanung oder Ausfall der Achsbegrenzung. Roboter faehrt ueber Software-Endschalter hinaus.",
|
ScenarioDE: "Roboterarm ueberschreitet Bewegungsbereich und trifft Schutzzaun. Person ausserhalb wird von Zaunteilen oder dem Roboterarm getroffen.",
|
||||||
|
TriggerDE: "Fehler in der Bahnplanung oder Ausfall der Achsbegrenzung.",
|
||||||
HarmDE: "Teile des Schutzzauns werden herausgeschleudert, Person ausserhalb wird getroffen.",
|
HarmDE: "Teile des Schutzzauns werden herausgeschleudert, Person ausserhalb wird getroffen.",
|
||||||
AffectedDE: "Bedienpersonal in der Naehe des Schutzzauns",
|
AffectedDE: "Bedienpersonal in der Naehe des Schutzzauns",
|
||||||
ZoneDE: "Schutzzaun, Bereich um die Roboterzelle",
|
ZoneDE: "Schutzzaun, Bereich um die Roboterzelle",
|
||||||
DefaultSeverity: 3, DefaultExposure: 2,
|
DefaultSeverity: 3, DefaultExposure: 2,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1605", NameDE: "Stoss durch Werkzeug/Greifer im Einrichtbetrieb", NameEN: "Impact by tool/gripper during setup",
|
||||||
|
RequiredComponentTags: []string{"moving_part", "clamping_part"},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M054"},
|
||||||
|
Priority: 98, MachineTypes: []string{"robotics_cobot", "automotive", "metalworking", "general_industry"},
|
||||||
|
ApplicableLifecycles: []string{"teach_mode", "setup", "changeover", "fault_clearing"},
|
||||||
|
ScenarioDE: "Person steht im Bewegungsbereich des Roboterarms und wird von bewegtem Werkzeug oder Greifer getroffen. Geschwindigkeitsreduzierung im Einrichtbetrieb reicht nicht aus.",
|
||||||
|
TriggerDE: "Roboter bewegt Werkzeug/Greifer mit unerwartet hoher Geschwindigkeit oder in unerwartete Richtung.",
|
||||||
|
HarmDE: "Prellungen, Quetschungen durch Kontakt mit Werkzeug/Greifer am Roboterarm.",
|
||||||
|
AffectedDE: "Einrichter, Programmierer, Wartungspersonal",
|
||||||
|
ZoneDE: "Inneres der Roboterzelle, Schwenkbereich Werkzeug/Greifer",
|
||||||
|
DefaultSeverity: 3, DefaultExposure: 3,
|
||||||
|
},
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// Greifer / Werkstueck
|
// Greifer / Werkstueck
|
||||||
// ================================================================
|
// ================================================================
|
||||||
{
|
{
|
||||||
ID: "HP1610", NameDE: "Quetschen im Greiferbereich des Roboters", NameEN: "Crushing in robot gripper area",
|
ID: "HP1610", NameDE: "Quetschen im Greiferbereich", NameEN: "Crushing in gripper area",
|
||||||
RequiredComponentTags: []string{"clamping_part"},
|
RequiredComponentTags: []string{"clamping_part"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M054", "M061"},
|
SuggestedMeasureIDs: []string{"M054", "M061"},
|
||||||
Priority: 94, MachineTypes: []string{"robotics_cobot", "automotive", "metalworking", "general_industry"},
|
Priority: 99, MachineTypes: []string{"robotics_cobot", "automotive", "metalworking", "general_industry"},
|
||||||
ScenarioDE: "Person greift in den Bereich des Greifers waehrend der Roboter ein Werkstueck aufnimmt oder ablegt. Hand wird zwischen Greifbacken und Werkstueck eingeklemmt.",
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "changeover", "fault_clearing"},
|
||||||
|
ScenarioDE: "Person greift in den Bereich des Greifers. Hand wird zwischen Greifbacken und Werkstueck eingeklemmt.",
|
||||||
TriggerDE: "Greiferbacken schliessen waehrend Koerperteil im Greifbereich ist.",
|
TriggerDE: "Greiferbacken schliessen waehrend Koerperteil im Greifbereich ist.",
|
||||||
HarmDE: "Quetschung oder Amputation von Fingern durch Greifkraft.",
|
HarmDE: "Quetschung oder Amputation von Fingern durch Greifkraft.",
|
||||||
AffectedDE: "Bedienpersonal, Einrichter",
|
AffectedDE: "Bedienpersonal, Einrichter",
|
||||||
@@ -95,8 +104,9 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
RequiredComponentTags: []string{"clamping_part"},
|
RequiredComponentTags: []string{"clamping_part"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M007", "M141"},
|
SuggestedMeasureIDs: []string{"M007", "M141"},
|
||||||
Priority: 93,
|
Priority: 98,
|
||||||
ScenarioDE: "Greifer verliert das Werkstueck waehrend des Transports (z.B. durch Druckverlust der Pneumatik, oelige Oberflaeche, falsches Werkstueck).",
|
ApplicableLifecycles: []string{"normal_operation", "setup", "changeover"},
|
||||||
|
ScenarioDE: "Greifer verliert das Werkstueck waehrend des Transports. Werkstueck faellt herab und trifft Person unterhalb des Roboterarms.",
|
||||||
TriggerDE: "Werkstueck faellt aus Greifer und trifft Person unterhalb des Roboterarms.",
|
TriggerDE: "Werkstueck faellt aus Greifer und trifft Person unterhalb des Roboterarms.",
|
||||||
HarmDE: "Prellungen, Knochenbrueche abhaengig von Werkstueckgewicht und Fallhoehe.",
|
HarmDE: "Prellungen, Knochenbrueche abhaengig von Werkstueckgewicht und Fallhoehe.",
|
||||||
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
||||||
@@ -108,8 +118,9 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
RequiredComponentTags: []string{"clamping_part", "guard"},
|
RequiredComponentTags: []string{"clamping_part", "guard"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M061", "M141"},
|
SuggestedMeasureIDs: []string{"M061", "M141"},
|
||||||
Priority: 92,
|
Priority: 98,
|
||||||
ScenarioDE: "Greifer versagt und Roboterarm beschleunigt das freigesetzte Werkstueck in Richtung Schutzzaun oder Einhausung.",
|
ApplicableLifecycles: []string{"normal_operation"},
|
||||||
|
ScenarioDE: "Greifer versagt und Werkstueck wird in Richtung Schutzzaun geschleudert. Person ausserhalb der Zelle wird von durchschlagendem Werkstueck getroffen.",
|
||||||
TriggerDE: "Werkstueck wird durch Roboterbewegung weggeschleudert und durchschlaegt die Schutzeinrichtung.",
|
TriggerDE: "Werkstueck wird durch Roboterbewegung weggeschleudert und durchschlaegt die Schutzeinrichtung.",
|
||||||
HarmDE: "Person ausserhalb der Zelle wird von weggeschleudertem Werkstueck getroffen.",
|
HarmDE: "Person ausserhalb der Zelle wird von weggeschleudertem Werkstueck getroffen.",
|
||||||
AffectedDE: "Bedienpersonal in der Naehe der Roboterzelle",
|
AffectedDE: "Bedienpersonal in der Naehe der Roboterzelle",
|
||||||
@@ -124,8 +135,9 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
RequiredComponentTags: []string{"entanglement_risk"},
|
RequiredComponentTags: []string{"entanglement_risk"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M002", "M061", "M003"},
|
SuggestedMeasureIDs: []string{"M002", "M061", "M003"},
|
||||||
Priority: 93,
|
Priority: 98,
|
||||||
ScenarioDE: "Person greift an Foerderband fuer Werkstueckzulauf oder -auslauf und wird zwischen beweglichen und feststehenden Teilen eingeklemmt.",
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "maintenance", "fault_clearing"},
|
||||||
|
ScenarioDE: "Person greift an Foerderband und wird zwischen beweglichen und feststehenden Teilen eingeklemmt.",
|
||||||
TriggerDE: "Hand oder Finger geraten zwischen Band und Umlenkrolle oder zwischen Werkstueck und Tunnelrahmen.",
|
TriggerDE: "Hand oder Finger geraten zwischen Band und Umlenkrolle oder zwischen Werkstueck und Tunnelrahmen.",
|
||||||
HarmDE: "Quetschung von Fingern, Einzug von Kleidung oder Haaren.",
|
HarmDE: "Quetschung von Fingern, Einzug von Kleidung oder Haaren.",
|
||||||
AffectedDE: "Bedienpersonal, Reinigungspersonal",
|
AffectedDE: "Bedienpersonal, Reinigungspersonal",
|
||||||
@@ -137,7 +149,8 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
RequiredComponentTags: []string{"entanglement_risk", "guard"},
|
RequiredComponentTags: []string{"entanglement_risk", "guard"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M002", "M061"},
|
SuggestedMeasureIDs: []string{"M002", "M061"},
|
||||||
Priority: 93,
|
Priority: 98,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "fault_clearing"},
|
||||||
ScenarioDE: "Person greift durch die Oeffnung im Schutzzaun fuer die Foerderbaender in den Gefahrenbereich des Roboters.",
|
ScenarioDE: "Person greift durch die Oeffnung im Schutzzaun fuer die Foerderbaender in den Gefahrenbereich des Roboters.",
|
||||||
TriggerDE: "Oeffnung ist zu gross oder Sicherheitsabstand zum Roboter-Schwenkbereich ist zu gering.",
|
TriggerDE: "Oeffnung ist zu gross oder Sicherheitsabstand zum Roboter-Schwenkbereich ist zu gering.",
|
||||||
HarmDE: "Quetschung von Hand oder Arm durch Roboterarm oder bewegte Maschinenteile.",
|
HarmDE: "Quetschung von Hand oder Arm durch Roboterarm oder bewegte Maschinenteile.",
|
||||||
@@ -150,9 +163,10 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
RequiredComponentTags: []string{"entanglement_risk"},
|
RequiredComponentTags: []string{"entanglement_risk"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M008"},
|
SuggestedMeasureIDs: []string{"M008"},
|
||||||
Priority: 91,
|
Priority: 97,
|
||||||
ScenarioDE: "Werkstueck faehrt ueber das Ende des Transportbandes hinaus und faellt herab, trifft Person die am Bandende steht.",
|
ApplicableLifecycles: []string{"normal_operation", "setup"},
|
||||||
TriggerDE: "Mechanischer Anschlag fehlt oder ist beschaedigt. Werkstueck wird nicht rechtzeitig gestoppt.",
|
ScenarioDE: "Werkstueck faehrt ueber das Ende des Transportbandes hinaus, faellt herab und trifft Person am Be-/Entladeplatz.",
|
||||||
|
TriggerDE: "Mechanischer Anschlag fehlt oder ist beschaedigt.",
|
||||||
HarmDE: "Prellungen, Quetschung von Fuessen durch herabfallendes Werkstueck.",
|
HarmDE: "Prellungen, Quetschung von Fuessen durch herabfallendes Werkstueck.",
|
||||||
AffectedDE: "Bedienpersonal am Be-/Entladeplatz",
|
AffectedDE: "Bedienpersonal am Be-/Entladeplatz",
|
||||||
ZoneDE: "Ende der Transportbaender, Be-/Entladeplatz",
|
ZoneDE: "Ende der Transportbaender, Be-/Entladeplatz",
|
||||||
@@ -166,8 +180,9 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
RequiredComponentTags: []string{"guard"},
|
RequiredComponentTags: []string{"guard"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M003"},
|
SuggestedMeasureIDs: []string{"M003"},
|
||||||
Priority: 90,
|
Priority: 97,
|
||||||
ScenarioDE: "Person schneidet sich an nicht entgrateten oder scharfkantigen Blechen der Einhausung, des Schutzzauns oder der Verkleidung.",
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "maintenance", "fault_clearing"},
|
||||||
|
ScenarioDE: "Person schneidet sich an nicht entgrateten oder scharfkantigen Blechen der Einhausung oder Verkleidung.",
|
||||||
TriggerDE: "Zugaengliche Kanten sind nicht gerundet oder gebrochen.",
|
TriggerDE: "Zugaengliche Kanten sind nicht gerundet oder gebrochen.",
|
||||||
HarmDE: "Schnittwunden an Haenden und Armen.",
|
HarmDE: "Schnittwunden an Haenden und Armen.",
|
||||||
AffectedDE: "Bedienpersonal, Wartungspersonal, Reinigungspersonal",
|
AffectedDE: "Bedienpersonal, Wartungspersonal, Reinigungspersonal",
|
||||||
@@ -178,11 +193,12 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
// Pneumatik / Druckluft
|
// Pneumatik / Druckluft
|
||||||
// ================================================================
|
// ================================================================
|
||||||
{
|
{
|
||||||
ID: "HP1630", NameDE: "Schlauch unter Druck springt ab", NameEN: "Pressurized hose comes loose",
|
ID: "HP1630", NameDE: "Pneumatikschlauch springt unter Druck ab", NameEN: "Pressurized hose comes loose",
|
||||||
RequiredComponentTags: []string{"pinch_point"},
|
RequiredComponentTags: []string{"pinch_point"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M420"},
|
SuggestedMeasureIDs: []string{"M480"},
|
||||||
Priority: 91,
|
Priority: 97,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "maintenance", "fault_clearing"},
|
||||||
ScenarioDE: "Pneumatikschlauch der Automation springt unter Druck ab und trifft eine Person (Peitscheneffekt).",
|
ScenarioDE: "Pneumatikschlauch der Automation springt unter Druck ab und trifft eine Person (Peitscheneffekt).",
|
||||||
TriggerDE: "Befestigung loest sich, Verschraubung wird undicht, Materialermuedung des Schlauchs.",
|
TriggerDE: "Befestigung loest sich, Verschraubung wird undicht, Materialermuedung des Schlauchs.",
|
||||||
HarmDE: "Prellungen, Augenverletzungen durch abspringenden Schlauch.",
|
HarmDE: "Prellungen, Augenverletzungen durch abspringenden Schlauch.",
|
||||||
@@ -194,11 +210,12 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
ID: "HP1631", NameDE: "Restdruck in Pneumatik nach Abschaltung", NameEN: "Residual pressure in pneumatics after shutdown",
|
ID: "HP1631", NameDE: "Restdruck in Pneumatik nach Abschaltung", NameEN: "Residual pressure in pneumatics after shutdown",
|
||||||
RequiredComponentTags: []string{"pinch_point"},
|
RequiredComponentTags: []string{"pinch_point"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M420", "M141"},
|
SuggestedMeasureIDs: []string{"M480", "M141"},
|
||||||
Priority: 91,
|
Priority: 97,
|
||||||
ScenarioDE: "Pneumatik-Komponenten stehen nach Abschaltung noch unter Druck. Bei Arbeiten an der Anlage werden druckbeaufschlagte Teile geloest.",
|
ApplicableLifecycles: []string{"maintenance", "fault_clearing", "changeover"},
|
||||||
TriggerDE: "Fehlende Druckentlastung vor Wartungsarbeiten. Gesperrte Rueckschlagventile halten Druck.",
|
ScenarioDE: "Person loest druckbeaufschlagte Pneumatik-Komponenten die nach Abschaltung noch unter Druck stehen. Teile fliegen unkontrolliert weg und treffen die Person.",
|
||||||
HarmDE: "Unkontrolliertes Loesen von Verbindungen, wegfliegende Teile, Verletzung durch Druckstoss.",
|
TriggerDE: "Fehlende Druckentlastung. Gesperrte Rueckschlagventile halten Druck.",
|
||||||
|
HarmDE: "Person wird von wegfliegenden Teilen oder unkontrolliert loesenden Verbindungen getroffen. Prellungen, Schnittverletzungen.",
|
||||||
AffectedDE: "Wartungspersonal, Einrichter",
|
AffectedDE: "Wartungspersonal, Einrichter",
|
||||||
ZoneDE: "Pneumatikschlaeuche und -komponenten",
|
ZoneDE: "Pneumatikschlaeuche und -komponenten",
|
||||||
DefaultSeverity: 2, DefaultExposure: 2,
|
DefaultSeverity: 2, DefaultExposure: 2,
|
||||||
@@ -207,14 +224,56 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
// Kuehlschmierstoff (KSS)
|
// Kuehlschmierstoff (KSS)
|
||||||
// ================================================================
|
// ================================================================
|
||||||
{
|
{
|
||||||
ID: "HP1635", NameDE: "KSS-Leckage fuehrt zu Rutschgefahr", NameEN: "Coolant leakage causes slip hazard",
|
ID: "HP1606", NameDE: "Quetschen/Scheren durch Greifer im Einrichtbetrieb", NameEN: "Crushing/shearing by gripper during setup",
|
||||||
|
RequiredComponentTags: []string{"clamping_part"},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M054"},
|
||||||
|
Priority: 98, MachineTypes: []string{"robotics_cobot", "automotive", "metalworking", "general_industry"},
|
||||||
|
ApplicableLifecycles: []string{"teach_mode", "setup", "changeover", "fault_clearing"},
|
||||||
|
ScenarioDE: "Einrichter steht im Schwenkbereich des Roboterarms und wird von bewegtem Greifer oder daran befestigtem Werkzeug verletzt.",
|
||||||
|
TriggerDE: "Reduzierte Geschwindigkeit im Einrichtbetrieb reicht nicht aus oder wird nicht aktiviert.",
|
||||||
|
HarmDE: "Quetschung, Schnittverletzung durch Greiferkanten oder Werkzeug am Roboter.",
|
||||||
|
AffectedDE: "Einrichter, Programmierer",
|
||||||
|
ZoneDE: "Inneres der Roboterzelle, Greifer/Werkzeug am Roboterarm",
|
||||||
|
DefaultSeverity: 3, DefaultExposure: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1634", NameDE: "KSS-Pumpe spritzt bei geoeffneter Schutztuer", NameEN: "Coolant pump sprays with open guard door",
|
||||||
|
RequiredComponentTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M061"},
|
||||||
|
Priority: 96, MachineTypes: []string{"cnc", "metalworking", "automotive"},
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "cleaning", "maintenance", "fault_clearing"},
|
||||||
|
ScenarioDE: "Niederdruck-Pumpe fuer Bettspuelung laeuft an waehrend Schutztuer geoeffnet ist. Person bekommt KSS-Spritzer ins Auge oder Gesicht.",
|
||||||
|
TriggerDE: "Pumpe startet automatisch, kein Verriegelungssignal von Schutztuer zur KSS-Pumpe.",
|
||||||
|
HarmDE: "Augenverletzung durch KSS-Spritzer, Hautreizung.",
|
||||||
|
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
||||||
|
ZoneDE: "Bearbeitungszelle, Austrittsduesen der Bettspuelung",
|
||||||
|
DefaultSeverity: 1, DefaultExposure: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1633", NameDE: "KSS-Versorgungsschlauch platzt oder reisst ab", NameEN: "Coolant supply hose bursts or tears off",
|
||||||
|
RequiredComponentTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M480"},
|
||||||
|
Priority: 97, MachineTypes: []string{"cnc", "metalworking", "automotive"},
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "maintenance", "fault_clearing"},
|
||||||
|
ScenarioDE: "KSS-Versorgungsschlauch reisst ab oder platzt. Person in der Naehe wird von abspringendem Schlauch oder KSS-Strahl unter Druck getroffen.",
|
||||||
|
TriggerDE: "Materialermuedung, mechanische Beschaedigung, fehlerhafte Befestigung des Schlauchs.",
|
||||||
|
HarmDE: "Person wird von KSS-Strahl getroffen. Einstichverletzung, Hautreizung, Rutschgefahr.",
|
||||||
|
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
||||||
|
ZoneDE: "Druckschlaeuche des Kuehlschmierstoffsystems, Verbindungsstellen",
|
||||||
|
DefaultSeverity: 2, DefaultExposure: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1635", NameDE: "Ausrutschen durch KSS-Leckage", NameEN: "Slipping due to coolant leakage",
|
||||||
RequiredComponentTags: []string{},
|
RequiredComponentTags: []string{},
|
||||||
RequiredEnergyTags: []string{},
|
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M420"},
|
SuggestedMeasureIDs: []string{"M420"},
|
||||||
Priority: 90, MachineTypes: []string{"cnc", "metalworking", "automotive"},
|
Priority: 97, MachineTypes: []string{"cnc", "metalworking", "automotive"},
|
||||||
ScenarioDE: "Kuehlschmierstoff tritt aus undichter Leitung oder Verbindung aus und bildet einen rutschigen Belag auf dem Boden.",
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "maintenance", "fault_clearing"},
|
||||||
TriggerDE: "Leckage an Schlauchverbindung, Dichtungsversagen, Ueberdrucksituation.",
|
ScenarioDE: "Kuehlschmierstoff tritt aus und bildet rutschigen Belag auf dem Boden. Person rutscht aus und stuerzt.",
|
||||||
|
TriggerDE: "Leckage an Schlauchverbindung, Dichtungsversagen.",
|
||||||
HarmDE: "Ausrutschen und Sturz, Prellungen, Knochenbrueche.",
|
HarmDE: "Ausrutschen und Sturz, Prellungen, Knochenbrueche.",
|
||||||
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
||||||
ZoneDE: "Boden um Bearbeitungszentrum und Kuehlschmierstoffanlage",
|
ZoneDE: "Boden um Bearbeitungszentrum und Kuehlschmierstoffanlage",
|
||||||
@@ -223,12 +282,12 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
{
|
{
|
||||||
ID: "HP1636", NameDE: "Hautkontakt mit Kuehlschmierstoff", NameEN: "Skin contact with coolant",
|
ID: "HP1636", NameDE: "Hautkontakt mit Kuehlschmierstoff", NameEN: "Skin contact with coolant",
|
||||||
RequiredComponentTags: []string{},
|
RequiredComponentTags: []string{},
|
||||||
RequiredEnergyTags: []string{},
|
|
||||||
GeneratedHazardCats: []string{"material_environmental"},
|
GeneratedHazardCats: []string{"material_environmental"},
|
||||||
SuggestedMeasureIDs: []string{"M141"},
|
SuggestedMeasureIDs: []string{"M141"},
|
||||||
Priority: 90, MachineTypes: []string{"cnc", "metalworking", "automotive"},
|
Priority: 97, MachineTypes: []string{"cnc", "metalworking", "automotive"},
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "maintenance", "fault_clearing"},
|
||||||
ScenarioDE: "Person kommt bei Arbeiten am Bearbeitungszentrum oder der Roboterzelle mit Kuehlschmierstoff in Beruehrung.",
|
ScenarioDE: "Person kommt bei Arbeiten am Bearbeitungszentrum oder der Roboterzelle mit Kuehlschmierstoff in Beruehrung.",
|
||||||
TriggerDE: "Hautkontakt beim Reinigen, Werkzeugwechsel oder Beseitigung von Stoerungen im Bearbeitungsraum.",
|
TriggerDE: "Hautkontakt beim Reinigen, Werkzeugwechsel oder Beseitigung von Stoerungen.",
|
||||||
HarmDE: "Hautirritationen, allergische Reaktionen, bei laengerer Exposition Ekzeme.",
|
HarmDE: "Hautirritationen, allergische Reaktionen, bei laengerer Exposition Ekzeme.",
|
||||||
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
||||||
ZoneDE: "Bearbeitungszentrum, Roboterzelle im Bereich der Beladetuer",
|
ZoneDE: "Bearbeitungszentrum, Roboterzelle im Bereich der Beladetuer",
|
||||||
@@ -237,13 +296,13 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
{
|
{
|
||||||
ID: "HP1637", NameDE: "Einatmen von KSS-Aerosolen", NameEN: "Inhalation of coolant aerosols",
|
ID: "HP1637", NameDE: "Einatmen von KSS-Aerosolen", NameEN: "Inhalation of coolant aerosols",
|
||||||
RequiredComponentTags: []string{},
|
RequiredComponentTags: []string{},
|
||||||
RequiredEnergyTags: []string{},
|
|
||||||
GeneratedHazardCats: []string{"material_environmental"},
|
GeneratedHazardCats: []string{"material_environmental"},
|
||||||
SuggestedMeasureIDs: []string{"M141"},
|
SuggestedMeasureIDs: []string{"M141"},
|
||||||
Priority: 90, MachineTypes: []string{"cnc", "metalworking", "automotive"},
|
Priority: 97, MachineTypes: []string{"cnc", "metalworking", "automotive"},
|
||||||
ScenarioDE: "Waehrend der Werkstueckbearbeitung entstehen KSS-Aerosole die beim Oeffnen der Bearbeitungszelle freigesetzt werden.",
|
ApplicableLifecycles: []string{"normal_operation", "setup", "maintenance"},
|
||||||
|
ScenarioDE: "Person oeffnet Schutztuer der Bearbeitungszelle und atmet freigesetzte KSS-Aerosole ein.",
|
||||||
TriggerDE: "Oeffnen der Schutztuer nach Bearbeitungsvorgang, unzureichende Absaugung.",
|
TriggerDE: "Oeffnen der Schutztuer nach Bearbeitungsvorgang, unzureichende Absaugung.",
|
||||||
HarmDE: "Atembeschwerden, Reizung der Atemwege, bei chronischer Exposition Atemwegserkrankungen.",
|
HarmDE: "Person atmet KSS-Aerosole ein. Atembeschwerden, Reizung der Atemwege, bei chronischer Exposition Atemwegserkrankungen.",
|
||||||
AffectedDE: "Bedienpersonal",
|
AffectedDE: "Bedienpersonal",
|
||||||
ZoneDE: "Bearbeitungszelle, Bereich vor der Schutztuer",
|
ZoneDE: "Bearbeitungszelle, Bereich vor der Schutztuer",
|
||||||
DefaultSeverity: 1, DefaultExposure: 3,
|
DefaultSeverity: 1, DefaultExposure: 3,
|
||||||
@@ -254,25 +313,27 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
{
|
{
|
||||||
ID: "HP1640", NameDE: "Direktes Beruehren spannungsfuehrender Teile", NameEN: "Direct contact with live parts",
|
ID: "HP1640", NameDE: "Direktes Beruehren spannungsfuehrender Teile", NameEN: "Direct contact with live parts",
|
||||||
RequiredComponentTags: []string{},
|
RequiredComponentTags: []string{},
|
||||||
RequiredEnergyTags: []string{"electrical"},
|
RequiredEnergyTags: []string{},
|
||||||
GeneratedHazardCats: []string{"electrical_hazard"},
|
GeneratedHazardCats: []string{"electrical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M009", "M410"},
|
SuggestedMeasureIDs: []string{"M265", "M089", "M088", "M139", "M475"},
|
||||||
Priority: 93,
|
Priority: 99,
|
||||||
ScenarioDE: "Person beruehrt spannungsfuehrende Teile der Anlage (Kabel, Klemmen, Stecker) die nicht ausreichend isoliert oder abgedeckt sind.",
|
ApplicableLifecycles: []string{"normal_operation", "setup", "maintenance", "fault_clearing"},
|
||||||
|
ScenarioDE: "Person beruehrt spannungsfuehrende Teile der Anlage die nicht ausreichend isoliert oder abgedeckt sind.",
|
||||||
TriggerDE: "Beschaedigte Isolation, fehlende Abdeckung, ungesicherter Schaltschrank.",
|
TriggerDE: "Beschaedigte Isolation, fehlende Abdeckung, ungesicherter Schaltschrank.",
|
||||||
HarmDE: "Elektrischer Schlag, bei Hochspannung potentiell toedlich.",
|
HarmDE: "Person erleidet elektrischen Schlag. Herzkammerflimmern, Verbrennungen, bei Hochspannung Todesfolge.",
|
||||||
AffectedDE: "Wartungspersonal, Einrichter",
|
AffectedDE: "Wartungspersonal, Einrichter",
|
||||||
ZoneDE: "Zugaengliche Kabel, Klemmen, Schaltschrank",
|
ZoneDE: "Zugaengliche Kabel, Klemmen, Schaltschrank",
|
||||||
DefaultSeverity: 4, DefaultExposure: 2,
|
DefaultSeverity: 4, DefaultExposure: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP1641", NameDE: "Indirektes Beruehren durch Schutzleiterfehler", NameEN: "Indirect contact due to PE conductor failure",
|
ID: "HP1641", NameDE: "Gefaehrliche Beruehrungsspannung durch Schutzleiterfehler", NameEN: "Dangerous touch voltage due to PE failure",
|
||||||
RequiredComponentTags: []string{},
|
RequiredComponentTags: []string{},
|
||||||
RequiredEnergyTags: []string{"electrical"},
|
RequiredEnergyTags: []string{"electrical"},
|
||||||
GeneratedHazardCats: []string{"electrical_hazard"},
|
GeneratedHazardCats: []string{"electrical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M410", "M411"},
|
SuggestedMeasureIDs: []string{"M475", "M476"},
|
||||||
Priority: 93,
|
Priority: 98,
|
||||||
ScenarioDE: "Schutzleiter ist unterbrochen oder nicht korrekt angeschlossen. Beruehrbare leitfaehige Teile fuehren gefaehrliche Beruehrungsspannung.",
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "maintenance", "fault_clearing"},
|
||||||
|
ScenarioDE: "Schutzleiter ist unterbrochen. Person beruehrt das Maschinengehaeuse und erleidet elektrischen Schlag durch gefaehrliche Beruehrungsspannung.",
|
||||||
TriggerDE: "Schutzleiterunterbrechung durch mechanische Beschaedigung oder fehlerhafte Installation.",
|
TriggerDE: "Schutzleiterunterbrechung durch mechanische Beschaedigung oder fehlerhafte Installation.",
|
||||||
HarmDE: "Elektrischer Schlag bei Beruehren des Maschinengehaeuses oder leitfaehiger Oberflaechen.",
|
HarmDE: "Elektrischer Schlag bei Beruehren des Maschinengehaeuses oder leitfaehiger Oberflaechen.",
|
||||||
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
||||||
@@ -285,9 +346,10 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
RequiredEnergyTags: []string{"electrical"},
|
RequiredEnergyTags: []string{"electrical"},
|
||||||
GeneratedHazardCats: []string{"electrical_hazard"},
|
GeneratedHazardCats: []string{"electrical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M009"},
|
SuggestedMeasureIDs: []string{"M009"},
|
||||||
Priority: 92,
|
Priority: 98,
|
||||||
ScenarioDE: "Kabelquerschnitt ist nicht auf die maximale Leistung ausgelegt oder Ueberstromschutz fehlt. Kabel ueberhitzt und entzuendet sich.",
|
ApplicableLifecycles: []string{"normal_operation", "setup", "maintenance"},
|
||||||
TriggerDE: "Dauerhafter Betrieb nahe der Belastungsgrenze, fehlende oder falsch dimensionierte Sicherung.",
|
ScenarioDE: "Kabel ueberhitzt und entzuendet sich durch Ueberlast oder fehlenden Ueberstromschutz. Person wird durch Brand oder toxische Gase verletzt.",
|
||||||
|
TriggerDE: "Dauerhafter Betrieb nahe der Belastungsgrenze, falsch dimensionierte Sicherung.",
|
||||||
HarmDE: "Brand, Rauchentwicklung, Verletzung durch Feuer oder toxische Gase.",
|
HarmDE: "Brand, Rauchentwicklung, Verletzung durch Feuer oder toxische Gase.",
|
||||||
AffectedDE: "Alle Personen im Bereich der Anlage",
|
AffectedDE: "Alle Personen im Bereich der Anlage",
|
||||||
ZoneDE: "Kabel und Leitungen der Anlage",
|
ZoneDE: "Kabel und Leitungen der Anlage",
|
||||||
|
|||||||
@@ -0,0 +1,465 @@
|
|||||||
|
package iace
|
||||||
|
|
||||||
|
// GetRobotCellPatternsExt returns additional hazard patterns for robot cells.
|
||||||
|
// These cover specific scenarios identified through GT benchmark gaps.
|
||||||
|
// HP1650-HP1699
|
||||||
|
func GetRobotCellPatternsExt() []HazardPattern {
|
||||||
|
return []HazardPattern{
|
||||||
|
// ================================================================
|
||||||
|
// Roboterarm — Spezifische Szenarien (GT-Gaps)
|
||||||
|
// ================================================================
|
||||||
|
{
|
||||||
|
ID: "HP1650", NameDE: "Roboterarm durchschlaegt Bewegungsbegrenzung", NameEN: "Robot arm exceeds motion limit",
|
||||||
|
RequiredComponentTags: []string{"moving_part", "guard"},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M061", "M054"},
|
||||||
|
Priority: 99,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "changeover", "fault_clearing"},
|
||||||
|
ScenarioDE: "Roboterarm ueberschreitet Bewegungsbegrenzung und trifft Schutzzaun. Person ausserhalb wird von Zaunteilen oder dem Roboterarm getroffen.",
|
||||||
|
TriggerDE: "Softwareendschalter versagt, Achsbegrenzung (DCS) fehlerhaft konfiguriert.",
|
||||||
|
HarmDE: "Person ausserhalb wird von Zaunteilen oder dem Roboterarm getroffen.",
|
||||||
|
AffectedDE: "Bedienpersonal in der Naehe des Schutzzauns",
|
||||||
|
ZoneDE: "Schutzzaun, Bereich um die Roboterzelle",
|
||||||
|
DefaultSeverity: 3, DefaultExposure: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1651", NameDE: "Wiederanlauf Roboter waehrend Person in Zelle", NameEN: "Robot restart while person inside cell",
|
||||||
|
RequiredComponentTags: []string{"moving_part", "guard"},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M054", "M061", "M141"},
|
||||||
|
Priority: 99,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "maintenance", "fault_clearing", "changeover"},
|
||||||
|
ScenarioDE: "Person befindet sich in der Roboterzelle. Schutztuer wird geschlossen und Roboter startet ohne dass sichergestellt ist, dass niemand im Gefahrenbereich ist.",
|
||||||
|
TriggerDE: "Fehlende Quittierungspflicht, kein Personenscanner, Schutztuer ohne Sicherheitszuhaltung.",
|
||||||
|
HarmDE: "Schwere Quetschungen, Knochenbrueche durch anlaufenden Roboter.",
|
||||||
|
AffectedDE: "Wartungspersonal, Einrichter, Reinigungspersonal",
|
||||||
|
ZoneDE: "Inneres der Roboterzelle, Roboterarm",
|
||||||
|
DefaultSeverity: 4, DefaultExposure: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1652", NameDE: "Quetschen durch Werkzeug/Greifer am Roboter im Betrieb", NameEN: "Crushing by tool/gripper during operation",
|
||||||
|
RequiredComponentTags: []string{"moving_part", "clamping_part"},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M054", "M061"},
|
||||||
|
Priority: 99,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning"},
|
||||||
|
ScenarioDE: "Person wird von bewegtem Werkzeug oder Greifer am Roboterarm getroffen oder zwischen Werkzeug und feststehenden Teilen eingeklemmt.",
|
||||||
|
TriggerDE: "Roboter bewegt Werkzeug/Greifer waehrend Person im Schwenkbereich.",
|
||||||
|
HarmDE: "Quetschungen, Schnittverletzungen, Prellungen durch Werkzeug/Greifer.",
|
||||||
|
AffectedDE: "Bedienpersonal, Einrichter",
|
||||||
|
ZoneDE: "Inneres der Roboterzelle, Greifer/Werkzeug des Roboterarms",
|
||||||
|
DefaultSeverity: 3, DefaultExposure: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1653", NameDE: "Quetschen durch Werkstück am Robotergreifer", NameEN: "Crushing by workpiece on robot gripper",
|
||||||
|
RequiredComponentTags: []string{"moving_part", "clamping_part"},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M054", "M061"},
|
||||||
|
Priority: 98,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "changeover"},
|
||||||
|
ScenarioDE: "Person wird von sich bewegendem Werkstueck am Robotergreifer getroffen oder zwischen Werkstueck und feststehenden Anlagenteilen eingeklemmt.",
|
||||||
|
TriggerDE: "Roboter transportiert Werkstueck, Person steht im Schwenkbereich.",
|
||||||
|
HarmDE: "Quetschungen, Prellungen, Knochenbrueche abhaengig von Werkstueckgewicht.",
|
||||||
|
AffectedDE: "Bedienpersonal, Einrichter",
|
||||||
|
ZoneDE: "Inneres der Roboterzelle, Greifer des Roboterarms",
|
||||||
|
DefaultSeverity: 3, DefaultExposure: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1654", NameDE: "Werkstück/Werkzeug durchschlaegt Schutzzaun", NameEN: "Workpiece/tool penetrates safety fence",
|
||||||
|
RequiredComponentTags: []string{"clamping_part", "guard"},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M061"},
|
||||||
|
Priority: 98,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation"},
|
||||||
|
ScenarioDE: "Greifer versagt und Werkstueck/Werkzeug wird Richtung Schutzzaun geschleudert. Person ausserhalb wird getroffen.",
|
||||||
|
TriggerDE: "Greifkraftverlust, Druckausfall, oelige Oberflaeche des Werkstuecks.",
|
||||||
|
HarmDE: "Person ausserhalb der Zelle wird von weggeschleudertem Teil getroffen.",
|
||||||
|
AffectedDE: "Bedienpersonal in der Naehe der Roboterzelle",
|
||||||
|
ZoneDE: "Schutzzaun, Bereich ausserhalb der Roboterzelle",
|
||||||
|
DefaultSeverity: 3, DefaultExposure: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1655", NameDE: "Durchgreifen ueber Schutzzaun zum Greifer/Werkstueck", NameEN: "Reaching over fence to gripper/workpiece",
|
||||||
|
RequiredComponentTags: []string{"clamping_part", "guard"},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M002", "M061"},
|
||||||
|
Priority: 98,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "maintenance", "fault_clearing"},
|
||||||
|
ScenarioDE: "Person greift ueber den Schutzzaun und erreicht den Greifer oder das Werkstueck am Roboterarm.",
|
||||||
|
TriggerDE: "Sicherheitsabstand zwischen Zaun-Oberkante und Greifer/Werkstueck zu gering.",
|
||||||
|
HarmDE: "Quetschung von Hand oder Arm zwischen Greifer/Werkstueck und feststehenden Teilen.",
|
||||||
|
AffectedDE: "Bedienpersonal",
|
||||||
|
ZoneDE: "Schutzzaun-Oberkante, Greifer/Werkstueck am Roboterarm",
|
||||||
|
DefaultSeverity: 3, DefaultExposure: 2,
|
||||||
|
},
|
||||||
|
// ================================================================
|
||||||
|
// Zentriergreifer an Förderbändern
|
||||||
|
// ================================================================
|
||||||
|
{
|
||||||
|
ID: "HP1660", NameDE: "Quetschen am Zentriergreifer von aussen", NameEN: "Crushing at centering gripper from outside",
|
||||||
|
RequiredComponentTags: []string{"clamping_part", "entanglement_risk"},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M002", "M061"},
|
||||||
|
Priority: 98,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "maintenance", "fault_clearing"},
|
||||||
|
ScenarioDE: "Person befindet sich ausserhalb der Roboterzelle und greift an die Zentriereinheit (fest montierter Greifer am Foerderband).",
|
||||||
|
TriggerDE: "Zentriergreifer schliesst waehrend Hand im Greifbereich. Unzureichender Abstand zwischen Greifer und Schutzzaun-Oeffnung.",
|
||||||
|
HarmDE: "Quetschung von Fingern oder Hand zwischen Greifbacken und Werkstueck.",
|
||||||
|
AffectedDE: "Bedienpersonal",
|
||||||
|
ZoneDE: "Zentriereinheit an Foerderbaendern, Schutzzaun-Oeffnung",
|
||||||
|
DefaultSeverity: 2, DefaultExposure: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1661", NameDE: "Quetschen am Zentriergreifer von innen", NameEN: "Crushing at centering gripper from inside cell",
|
||||||
|
RequiredComponentTags: []string{"clamping_part", "entanglement_risk", "guard"},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M054", "M061"},
|
||||||
|
Priority: 98,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "cleaning", "fault_clearing"},
|
||||||
|
ScenarioDE: "Person befindet sich innerhalb der Roboterzelle und greift an die Zentriereinheit am Foerderband.",
|
||||||
|
TriggerDE: "Schutztuer geoeffnet, aber Zentriergreifer wird nicht automatisch stillgesetzt.",
|
||||||
|
HarmDE: "Quetschung von Fingern oder Hand zwischen Greifbacken und Werkstueck.",
|
||||||
|
AffectedDE: "Wartungspersonal, Reinigungspersonal",
|
||||||
|
ZoneDE: "Zentriereinheit an Foerderbaendern innerhalb der Roboterzelle",
|
||||||
|
DefaultSeverity: 2, DefaultExposure: 3,
|
||||||
|
},
|
||||||
|
// ================================================================
|
||||||
|
// Bearbeitungszentrum (Robodrill/WZM) innerhalb Roboterzelle
|
||||||
|
// ================================================================
|
||||||
|
{
|
||||||
|
ID: "HP1665", NameDE: "Quetschen an Beladetuer der Werkzeugmaschine", NameEN: "Crushing at machine tool loading door",
|
||||||
|
RequiredComponentTags: []string{"moving_part"},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M054", "M061"},
|
||||||
|
Priority: 98, MachineTypes: []string{"cnc", "metalworking", "automotive", "robotics_cobot"},
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "maintenance", "fault_clearing"},
|
||||||
|
ScenarioDE: "Person greift durch die Beladetuer der Werkzeugmaschine. Beladetuer schliesst sich oder bewegliche Teile im Innenraum starten.",
|
||||||
|
TriggerDE: "Tuerpositionsschalter nicht in Robotersteuerung eingebunden, fehlende Verriegelung.",
|
||||||
|
HarmDE: "Quetschung von Hand/Arm an Beladetuer oder durch bewegliche Teile im Bearbeitungsraum.",
|
||||||
|
AffectedDE: "Bedienpersonal, Einrichter, Wartungspersonal",
|
||||||
|
ZoneDE: "Beladetuer der Werkzeugmaschine, Bearbeitungsraum",
|
||||||
|
DefaultSeverity: 3, DefaultExposure: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1666", NameDE: "Quetschen/Scheren im Bearbeitungsraum der WZM", NameEN: "Crushing/shearing inside machine tool workspace",
|
||||||
|
RequiredComponentTags: []string{"moving_part"},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M054"},
|
||||||
|
Priority: 98, MachineTypes: []string{"cnc", "metalworking", "automotive", "robotics_cobot"},
|
||||||
|
ApplicableLifecycles: []string{"setup", "maintenance", "fault_clearing"},
|
||||||
|
ScenarioDE: "Person greift in den Bearbeitungsraum der Werkzeugmaschine und wird von beweglichen Achsen, Werkzeug oder Spannvorrichtung verletzt.",
|
||||||
|
TriggerDE: "Bewegliche Teile starten waehrend Hand im Bearbeitungsraum (Einrichtbetrieb, Stoerungsbeseitigung).",
|
||||||
|
HarmDE: "Quetschungen, Schnittverletzungen durch rotierende Werkzeuge, Scheren an Achsbewegungen.",
|
||||||
|
AffectedDE: "Einrichter, Wartungspersonal",
|
||||||
|
ZoneDE: "Bearbeitungsraum der Werkzeugmaschine, Achsen, Werkzeug, Spannvorrichtung",
|
||||||
|
DefaultSeverity: 3, DefaultExposure: 3,
|
||||||
|
},
|
||||||
|
// ================================================================
|
||||||
|
// KSS-Spritzer / Druckluft in Bearbeitungszelle
|
||||||
|
// ================================================================
|
||||||
|
{
|
||||||
|
ID: "HP1670", NameDE: "KSS-Spritzer in Augen/Gesicht", NameEN: "Coolant splash to eyes/face",
|
||||||
|
RequiredComponentTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M141"},
|
||||||
|
Priority: 97, MachineTypes: []string{"cnc", "metalworking", "automotive"},
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "cleaning", "maintenance", "fault_clearing"},
|
||||||
|
ScenarioDE: "Person bekommt Kuehlschmierstoff-Spritzer ins Auge oder Gesicht beim Oeffnen der Bearbeitungszelle oder bei laufender Bettspuelung.",
|
||||||
|
TriggerDE: "KSS-Pumpe laeuft waehrend Schutztuer geoeffnet ist, Austrittsduese nicht korrekt gerichtet.",
|
||||||
|
HarmDE: "Augenverletzung, Reizung der Bindehaut, bei Hochdruck-KSS ernsthafte Augenschaeden.",
|
||||||
|
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
||||||
|
ZoneDE: "Bearbeitungszelle, Bereich vor der Schutztuer, Austrittsduesen",
|
||||||
|
DefaultSeverity: 2, DefaultExposure: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1671", NameDE: "Druckluft-Verletzung in Bearbeitungszelle", NameEN: "Compressed air injury in machining cell",
|
||||||
|
RequiredComponentTags: []string{"pinch_point"},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M061"},
|
||||||
|
Priority: 97, MachineTypes: []string{"cnc", "metalworking", "automotive"},
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "cleaning", "maintenance", "fault_clearing"},
|
||||||
|
ScenarioDE: "Person wird von ausstroemender Druckluft oder aufgewirbelten Bearbeitungsrueckstaenden in der Bearbeitungszelle verletzt.",
|
||||||
|
TriggerDE: "Druckluftreinigungsduese aktiv waehrend Schutztuer geoeffnet, Spaene oder Partikel werden aufgewirbelt.",
|
||||||
|
HarmDE: "Augenverletzung durch Spaene, Hautverletzung durch Druckluftstoss.",
|
||||||
|
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
||||||
|
ZoneDE: "Bearbeitungszelle, Druckluftreinigungsduesen",
|
||||||
|
DefaultSeverity: 2, DefaultExposure: 3,
|
||||||
|
},
|
||||||
|
// ================================================================
|
||||||
|
// KSS-Schläuche unter Druck
|
||||||
|
// ================================================================
|
||||||
|
{
|
||||||
|
ID: "HP1675", NameDE: "KSS-Schlauch bersten oder abspringen", NameEN: "Coolant hose burst or detachment",
|
||||||
|
RequiredComponentTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M480"},
|
||||||
|
Priority: 97, MachineTypes: []string{"cnc", "metalworking", "automotive"},
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "maintenance", "fault_clearing"},
|
||||||
|
ScenarioDE: "Schlauch der Kuehlschmierstoffversorgung zwischen Aufbereitungsanlage und Bearbeitungszentrum platzt oder springt unter Druck ab.",
|
||||||
|
TriggerDE: "Materialermuedung, Ueberdruck, fehlerhafte Befestigung, mechanische Beschaedigung des Schlauchs.",
|
||||||
|
HarmDE: "Person wird von abspringendem Schlauch getroffen (Peitscheneffekt). KSS-Spritzer unter Druck verletzen Haut und Augen. Rutschgefahr durch austretenden KSS.",
|
||||||
|
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
||||||
|
ZoneDE: "Druckschlaeuche des Kuehlschmierstoffsystems",
|
||||||
|
DefaultSeverity: 2, DefaultExposure: 2,
|
||||||
|
},
|
||||||
|
// ================================================================
|
||||||
|
// Quetschen am Förderband — Werkstück/Tunnel
|
||||||
|
// ================================================================
|
||||||
|
{
|
||||||
|
ID: "HP1680", NameDE: "Quetschen zwischen Werkstueck und Tunnel am Foerderband", NameEN: "Crushing between workpiece and conveyor tunnel",
|
||||||
|
RequiredComponentTags: []string{"entanglement_risk"},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M002", "M003"},
|
||||||
|
Priority: 97,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "fault_clearing"},
|
||||||
|
ScenarioDE: "Person greift an den Tunnel/Rahmen des Foerderbandes und wird von einem darauf bewegten Werkstueck eingequetscht.",
|
||||||
|
TriggerDE: "Zu geringer Abstand zwischen Werkstueck und Tunnel/Rahmen, scharfe Kanten an Tunneleingang.",
|
||||||
|
HarmDE: "Quetschung von Fingern zwischen Werkstueck und Rahmen.",
|
||||||
|
AffectedDE: "Bedienpersonal",
|
||||||
|
ZoneDE: "Foerderband-Tunnel, Werkstück auf dem Band",
|
||||||
|
DefaultSeverity: 2, DefaultExposure: 3,
|
||||||
|
},
|
||||||
|
// ================================================================
|
||||||
|
// Elektrisch — Spezifische Szenarien
|
||||||
|
// ================================================================
|
||||||
|
{
|
||||||
|
ID: "HP1685", NameDE: "Indirektes Beruehren durch Schutzleiterunterbrechung", NameEN: "Indirect contact due to PE interruption",
|
||||||
|
RequiredComponentTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"electrical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M475", "M476"},
|
||||||
|
Priority: 98,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "maintenance", "fault_clearing"},
|
||||||
|
ScenarioDE: "Schutzleiter ist unterbrochen. Person beruehrt leitfaehige Maschinenteile und erleidet elektrischen Schlag.",
|
||||||
|
TriggerDE: "Mechanische Beschaedigung des Schutzleiters, korrodierte Verbindung, fehlerhafte Installation.",
|
||||||
|
HarmDE: "Elektrischer Schlag bei Beruehren des Maschinengehaeuses oder anderer leitfaehiger Teile.",
|
||||||
|
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
||||||
|
ZoneDE: "Beruehrbare leitfaehige Oberflaechen der Anlage",
|
||||||
|
DefaultSeverity: 4, DefaultExposure: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1686", NameDE: "Direktes Beruehren im Schaltschrank", NameEN: "Direct contact inside control cabinet",
|
||||||
|
RequiredComponentTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"electrical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M009"},
|
||||||
|
Priority: 98,
|
||||||
|
ApplicableLifecycles: []string{"maintenance", "fault_clearing", "commissioning"},
|
||||||
|
ScenarioDE: "Person beruehrt spannungsfuehrende Teile bei geoeffnetem Schaltschrank. Leiter um Bedienelemente sind nicht fingersicher geschuetzt.",
|
||||||
|
TriggerDE: "Schaltschranktuer geoeffnet fuer Wartung oder Fehlersuche, unzureichender Beruehrungsschutz.",
|
||||||
|
HarmDE: "Person erleidet elektrischen Schlag. Herzkammerflimmern, Verbrennungen, bei Hochspannung Todesfolge.",
|
||||||
|
AffectedDE: "Wartungspersonal, Elektrofachkraefte",
|
||||||
|
ZoneDE: "Schaltschrank-Innenraum, Klemmen, Sammelschienen",
|
||||||
|
DefaultSeverity: 4, DefaultExposure: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1687", NameDE: "Brand durch eindringende Fluessigkeit", NameEN: "Fire from liquid ingress causing short circuit",
|
||||||
|
RequiredComponentTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"electrical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M009"},
|
||||||
|
Priority: 98,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "cleaning"},
|
||||||
|
ScenarioDE: "Fluessigkeit dringt in elektrische Komponenten ein und verursacht Kurzschluss. Person wird durch Brand oder Rauchentwicklung gefaehrdet.",
|
||||||
|
TriggerDE: "Reinigung mit Wasser, KSS-Leckage tropft auf Schaltschrank oder Steuerungskomponenten.",
|
||||||
|
HarmDE: "Person wird durch Brand, Flammen oder toxische Rauchgase verletzt. Verbrennungen, Rauchvergiftung.",
|
||||||
|
AffectedDE: "Bedienpersonal, Reinigungspersonal",
|
||||||
|
ZoneDE: "Schaltgeraetekombinationen, elektrische Komponenten unterhalb von Rohrleitungen",
|
||||||
|
DefaultSeverity: 3, DefaultExposure: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1688", NameDE: "Gefaehrliche Beruehrungsspannung durch Potentialunterschiede", NameEN: "Dangerous touch voltage from potential differences",
|
||||||
|
RequiredComponentTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"electrical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M475", "M477", "M138", "M329"},
|
||||||
|
Priority: 96,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "maintenance", "fault_clearing"},
|
||||||
|
ScenarioDE: "Person beruehrt gleichzeitig Anlagenteile mit unterschiedlichem Potential und erleidet elektrischen Schlag.",
|
||||||
|
TriggerDE: "Fehlender Potentialausgleich zwischen Anlagenteilen verschiedener Hersteller.",
|
||||||
|
HarmDE: "Elektrischer Schlag bei gleichzeitigem Beruehren von Teilen unterschiedlichen Potentials.",
|
||||||
|
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
||||||
|
ZoneDE: "Elektrisch leitfaehige Oberflaechen verschiedener Anlagenteile",
|
||||||
|
DefaultSeverity: 4, DefaultExposure: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1689", NameDE: "Fehlerstromschutz an Steckdosenstromkreisen", NameEN: "RCD protection at socket circuits",
|
||||||
|
RequiredComponentTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"electrical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M475"},
|
||||||
|
Priority: 97,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "maintenance", "fault_clearing"},
|
||||||
|
ScenarioDE: "Defektes Geraet wird an Steckdose der Maschine angeschlossen. Fehlerstrom fliesst ueber den Koerper der beruerenden Person.",
|
||||||
|
TriggerDE: "Fehlende Fehlerstrom-Schutzeinrichtung (RCD) an Steckdosenstromkreisen der Maschine.",
|
||||||
|
HarmDE: "Person erleidet elektrischen Schlag durch Fehlerstrom. Herzkammerflimmern, potentiell toedlich.",
|
||||||
|
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
||||||
|
ZoneDE: "Steckdosen der Maschine, angeschlossene Betriebsmittel",
|
||||||
|
DefaultSeverity: 4, DefaultExposure: 2,
|
||||||
|
},
|
||||||
|
// ================================================================
|
||||||
|
// Ergonomie
|
||||||
|
// ================================================================
|
||||||
|
{
|
||||||
|
ID: "HP1690", NameDE: "Ergonomisch unguenstige Einlegeposition", NameEN: "Unfavorable ergonomic loading position",
|
||||||
|
RequiredComponentTags: []string{"entanglement_risk"},
|
||||||
|
GeneratedHazardCats: []string{"ergonomic_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{},
|
||||||
|
Priority: 85,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation"},
|
||||||
|
ScenarioDE: "Person muss Werkstuecke in ergonomisch unguenstiger Hoehe oder Reichweite auf das Foerderband auflegen oder entnehmen.",
|
||||||
|
TriggerDE: "Bandhoehe nicht auf ergonomische Handhabung ausgelegt, schwere Werkstuecke.",
|
||||||
|
HarmDE: "Person erleidet Rueckenbeschwerden und Schulterbelastung durch wiederholte Fehlhaltung. Langfristig Muskel-Skelett-Erkrankungen.",
|
||||||
|
AffectedDE: "Bedienpersonal",
|
||||||
|
ZoneDE: "Beladebereich der Foerderbaender",
|
||||||
|
DefaultSeverity: 2, DefaultExposure: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1691", NameDE: "Unergonomische Position der Bedienelemente", NameEN: "Unfavorable ergonomic position of controls",
|
||||||
|
RequiredComponentTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"ergonomic_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{},
|
||||||
|
Priority: 85,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup"},
|
||||||
|
ScenarioDE: "Person bedient Anlage in ergonomisch unguenstiger Position ueber laengere Zeit.",
|
||||||
|
TriggerDE: "Bedienfeld zu hoch, zu niedrig oder seitlich versetzt montiert.",
|
||||||
|
HarmDE: "Person erleidet Nacken- und Schulterbelastung durch unguenstige Bedienposition. Langfristig Haltungsschaeden.",
|
||||||
|
AffectedDE: "Bedienpersonal",
|
||||||
|
ZoneDE: "Bedienfeld, HMI, Betriebsartenwahlschalter",
|
||||||
|
DefaultSeverity: 2, DefaultExposure: 4,
|
||||||
|
},
|
||||||
|
// ================================================================
|
||||||
|
// Thermisch / Verbrennung
|
||||||
|
// ================================================================
|
||||||
|
{
|
||||||
|
ID: "HP1695", NameDE: "Verbrennung an heissen Werkstuecken", NameEN: "Burn from hot workpieces",
|
||||||
|
RequiredComponentTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"thermal_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M141"},
|
||||||
|
Priority: 88, MachineTypes: []string{"cnc", "metalworking", "automotive"},
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "changeover"},
|
||||||
|
ScenarioDE: "Person beruehrt heisse Werkstuecke die durch die Bearbeitung erwaermt wurden.",
|
||||||
|
TriggerDE: "Manuelle Entnahme von Werkstuecken ohne Wartezeit oder Schutzhandschuhe.",
|
||||||
|
HarmDE: "Verbrennungen an Haenden und Fingern.",
|
||||||
|
AffectedDE: "Bedienpersonal",
|
||||||
|
ZoneDE: "Werkstueckausgabe, Entnahmeplatz",
|
||||||
|
DefaultSeverity: 1, DefaultExposure: 3,
|
||||||
|
},
|
||||||
|
// ================================================================
|
||||||
|
// Tragfähigkeit / Aufstellung
|
||||||
|
// ================================================================
|
||||||
|
{
|
||||||
|
ID: "HP1697", NameDE: "Anlage bricht durch unzureichenden Untergrund ein", NameEN: "Machine collapses through insufficient floor",
|
||||||
|
RequiredComponentTags: []string{"high_force"},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{},
|
||||||
|
Priority: 88,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "commissioning"},
|
||||||
|
ScenarioDE: "Untergrund bricht unter dem Maschinengewicht ein. Personen im Umfeld werden von kippender oder absackender Anlage eingeklemmt.",
|
||||||
|
TriggerDE: "Boden nicht auf maximale statische und dynamische Lasten der Maschine ausgelegt.",
|
||||||
|
HarmDE: "Anlage bricht ein, Quetschung von Personen im Umfeld.",
|
||||||
|
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
||||||
|
ZoneDE: "Bereich um die Maschine, Aufstellflaeche",
|
||||||
|
DefaultSeverity: 4, DefaultExposure: 1,
|
||||||
|
},
|
||||||
|
// ================================================================
|
||||||
|
// Elektrisch — Kriechstrecken + EMV
|
||||||
|
// ================================================================
|
||||||
|
{
|
||||||
|
ID: "HP1698", NameDE: "Kurzschluss durch unzureichende Luft-/Kriechstrecken", NameEN: "Short circuit from insufficient creepage/clearance",
|
||||||
|
RequiredComponentTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"electrical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M477"},
|
||||||
|
Priority: 98,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "maintenance", "fault_clearing"},
|
||||||
|
ScenarioDE: "Unzureichende Luft-/Kriechstrecken fuehren bei Verschmutzung zu Kriechstroemen. Person beruehrt betroffene Teile und erleidet elektrischen Schlag.",
|
||||||
|
TriggerDE: "Verschmutzungsgrad hoeher als bei der Dimensionierung angenommen, Feuchtigkeit, alterungsbedingte Veraenderung.",
|
||||||
|
HarmDE: "Gefaehrliche Beruehrungsspannung an beruehrbaren Teilen, Kurzschluss, Brand.",
|
||||||
|
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
||||||
|
ZoneDE: "Schaltgeraetekombinationen, elektrische Anschluesse",
|
||||||
|
DefaultSeverity: 4, DefaultExposure: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1699", NameDE: "EMV-Stoereinfluss auf Sicherheitsfunktionen", NameEN: "EMC interference with safety functions",
|
||||||
|
RequiredComponentTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"radiation_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M478", "M479"},
|
||||||
|
Priority: 97,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup"},
|
||||||
|
ScenarioDE: "EMV-Stoerungen verursachen unerwartete Maschinenbewegungen. Person im Gefahrenbereich wird von unkontrolliert bewegten Teilen getroffen.",
|
||||||
|
TriggerDE: "Unzureichende EMV-Schirmung, nicht-fachgerechte Verkabelung, externe Stoerquellen.",
|
||||||
|
HarmDE: "Unkontrollierte Bewegung von Achsen, Werkzeug oder Roboterarm durch Steuerungsfehler.",
|
||||||
|
AffectedDE: "Bedienpersonal, Einrichter",
|
||||||
|
ZoneDE: "Bearbeitungsbereich, sicherheitsrelevante Steuerungen",
|
||||||
|
DefaultSeverity: 3, DefaultExposure: 2,
|
||||||
|
},
|
||||||
|
// ================================================================
|
||||||
|
// Differenzierte Patterns (GT-Benchmark: gleiche Zone, anderes Szenario)
|
||||||
|
// ================================================================
|
||||||
|
{
|
||||||
|
ID: "HP1700", NameDE: "Getroffen von bewegtem Werkzeug/Greifer am Roboter", NameEN: "Struck by moving tool/gripper on robot",
|
||||||
|
RequiredComponentTags: []string{"moving_part", "clamping_part"},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M054", "M061"},
|
||||||
|
Priority: 99,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "teach_mode", "cleaning"},
|
||||||
|
ScenarioDE: "Person steht im Bewegungsbereich des Roboterarms und wird von bewegtem Werkzeug oder Greifer getroffen.",
|
||||||
|
TriggerDE: "Roboter schwenkt mit Werkzeug/Greifer in Richtung Person.",
|
||||||
|
HarmDE: "Prellungen, Schnittverletzungen durch Werkzeugkanten, Knochenbrueche.",
|
||||||
|
AffectedDE: "Bedienpersonal, Einrichter",
|
||||||
|
ZoneDE: "Inneres der Roboterzelle, Schwenkbereich Werkzeug/Greifer",
|
||||||
|
DefaultSeverity: 3, DefaultExposure: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1701", NameDE: "Greifer/Werkzeug durchschlaegt Schutzzaun", NameEN: "Gripper/tool penetrates safety fence",
|
||||||
|
RequiredComponentTags: []string{"clamping_part", "guard"},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M061"},
|
||||||
|
Priority: 98,
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "setup", "changeover"},
|
||||||
|
ScenarioDE: "Greifer oder Werkzeug am Roboterarm durchschlaegt den Schutzzaun und trifft Person ausserhalb der Zelle.",
|
||||||
|
TriggerDE: "Bewegungsbegrenzung versagt, Schutzzaun nicht auf Aufprallenergie ausgelegt.",
|
||||||
|
HarmDE: "Person ausserhalb wird von Greifer/Werkzeug oder Zaunteilen getroffen.",
|
||||||
|
AffectedDE: "Bedienpersonal in der Naehe des Schutzzauns",
|
||||||
|
ZoneDE: "Bereich um Roboterarm ausserhalb der Roboterzelle",
|
||||||
|
DefaultSeverity: 3, DefaultExposure: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1702", NameDE: "KSS-Schlauch platzt unter Druck", NameEN: "Coolant hose bursts under pressure",
|
||||||
|
RequiredComponentTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M480"},
|
||||||
|
Priority: 97, MachineTypes: []string{"cnc", "metalworking", "automotive"},
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "maintenance", "fault_clearing"},
|
||||||
|
ScenarioDE: "KSS-Schlauch platzt und spritzt Kuehlschmierstoff unter Druck. Person in der Naehe wird von KSS-Strahl getroffen.",
|
||||||
|
TriggerDE: "Alterung, Beschaedigung oder Ueberdruck fuehrt zum Versagen des Schlauchs.",
|
||||||
|
HarmDE: "Einstichverletzung durch KSS-Strahl unter Druck, Augenverletzung, Rutschgefahr.",
|
||||||
|
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
||||||
|
ZoneDE: "Druckschlaeuche des Kuehlschmierstoffsystems",
|
||||||
|
DefaultSeverity: 2, DefaultExposure: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1703", NameDE: "KSS-Bettspuelung bei geoeffneter Schutztuer", NameEN: "Coolant bed wash with open guard door",
|
||||||
|
RequiredComponentTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M061"},
|
||||||
|
Priority: 97, MachineTypes: []string{"cnc", "metalworking", "automotive"},
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "cleaning", "maintenance", "fault_clearing"},
|
||||||
|
ScenarioDE: "KSS-Pumpe laeuft bei geoeffneter Schutztuer. Person vor der Bearbeitungszelle bekommt KSS-Spritzer ins Auge oder Gesicht.",
|
||||||
|
TriggerDE: "Kein automatisches Abschalten der KSS-Pumpe bei geoeffneter Tuer.",
|
||||||
|
HarmDE: "KSS-Spritzer in Augen oder Gesicht, Rutschgefahr durch austretenden KSS.",
|
||||||
|
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
||||||
|
ZoneDE: "Inneres des Bearbeitungszentrums, Bereich vor der Schutztuer",
|
||||||
|
DefaultSeverity: 1, DefaultExposure: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP1704", NameDE: "Brand durch KSS-Leckage auf elektrische Komponenten", NameEN: "Fire from coolant leakage on electrical components",
|
||||||
|
RequiredComponentTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"electrical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M480", "M009"},
|
||||||
|
Priority: 98, MachineTypes: []string{"cnc", "metalworking", "automotive"},
|
||||||
|
ApplicableLifecycles: []string{"normal_operation", "cleaning", "maintenance"},
|
||||||
|
ScenarioDE: "KSS-Leckage tropft auf elektrische Komponenten und verursacht Kurzschluss. Person wird durch Brand oder Rauchentwicklung gefaehrdet.",
|
||||||
|
TriggerDE: "KSS-Leitung undicht oberhalb elektrischer Komponenten, tropft auf Klemmen oder Leiterplatten.",
|
||||||
|
HarmDE: "Person wird durch Brand, Flammen oder toxische Rauchgase verletzt. Verbrennungen, Rauchvergiftung.",
|
||||||
|
AffectedDE: "Bedienpersonal",
|
||||||
|
ZoneDE: "Spannungsfuehrende Teile unterhalb/angrenzend von KSS-Leitungen",
|
||||||
|
DefaultSeverity: 3, DefaultExposure: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,7 +42,7 @@ func builtinSoftwarePatterns() []HazardPattern {
|
|||||||
SuggestedMeasureIDs: []string{"M145", "M146", "M121"},
|
SuggestedMeasureIDs: []string{"M145", "M146", "M121"},
|
||||||
SuggestedEvidenceIDs: []string{"E01", "E14"},
|
SuggestedEvidenceIDs: []string{"E01", "E14"},
|
||||||
Priority: 70,
|
Priority: 70,
|
||||||
ScenarioDE: "Falsche Parametrierung von Achsgrenzen, Geschwindigkeiten oder Sicherheitsgrenzen nach Umruestung.",
|
ScenarioDE: "Falsche Parametrierung von Achsgrenzen, Geschwindigkeiten oder Sicherheitsgrenzen nach Produktwechsel.",
|
||||||
TriggerDE: "Bediener oder Einrichter aendert Parameter ohne Validierung oder nutzt falsches Rezept/Programm.",
|
TriggerDE: "Bediener oder Einrichter aendert Parameter ohne Validierung oder nutzt falsches Rezept/Programm.",
|
||||||
HarmDE: "Ueberfahren mechanischer Anschlaege, zu hohe Kraefte/Geschwindigkeiten, Kollision.",
|
HarmDE: "Ueberfahren mechanischer Anschlaege, zu hohe Kraefte/Geschwindigkeiten, Kollision.",
|
||||||
AffectedDE: "Bedienpersonal, Einrichter",
|
AffectedDE: "Bedienpersonal, Einrichter",
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ func GetSpecificMachinePatterns() []HazardPattern {
|
|||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M003", "M141"},
|
SuggestedMeasureIDs: []string{"M003", "M141"},
|
||||||
SuggestedEvidenceIDs: []string{"E01", "E20"},
|
SuggestedEvidenceIDs: []string{"E01", "E20"},
|
||||||
Priority: 90,
|
Priority: 90, MachineTypes: []string{"wind_turbine"},
|
||||||
ScenarioDE: "Rotorblatt einer Windturbine bricht durch Materialermuedung oder Blitzschlag und wird Hunderte Meter weit geschleudert.",
|
ScenarioDE: "Rotorblatt einer Windturbine bricht durch Materialermuedung oder Blitzschlag und wird Hunderte Meter weit geschleudert.",
|
||||||
TriggerDE: "Materialermuedung, Blitzschaden, Vereisung mit Unwucht, fehlende Inspektionen",
|
TriggerDE: "Materialermuedung, Blitzschaden, Vereisung mit Unwucht, fehlende Inspektionen",
|
||||||
HarmDE: "Toedliche Verletzung durch Blattstuecke, Sachschaeden im weiten Umkreis",
|
HarmDE: "Toedliche Verletzung durch Blattstuecke, Sachschaeden im weiten Umkreis",
|
||||||
@@ -261,7 +261,7 @@ func GetSpecificMachinePatterns() []HazardPattern {
|
|||||||
DefaultSeverity: 5, DefaultExposure: 1,
|
DefaultSeverity: 5, DefaultExposure: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP746", NameDE: "Absturz bei Wartung der Gondel", NameEN: "Fall during nacelle maintenance",
|
ID: "HP746", NameDE: "Absturz der Gondel", NameEN: "Fall during nacelle maintenance",
|
||||||
RequiredComponentTags: []string{"structural_part", "gravity_risk"},
|
RequiredComponentTags: []string{"structural_part", "gravity_risk"},
|
||||||
RequiredEnergyTags: []string{"gravitational"},
|
RequiredEnergyTags: []string{"gravitational"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
@@ -297,7 +297,7 @@ func GetSpecificMachinePatterns() []HazardPattern {
|
|||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M141"},
|
SuggestedMeasureIDs: []string{"M141"},
|
||||||
SuggestedEvidenceIDs: []string{"E01", "E20"},
|
SuggestedEvidenceIDs: []string{"E01", "E20"},
|
||||||
Priority: 80,
|
Priority: 80, MachineTypes: []string{"wind_turbine"},
|
||||||
ScenarioDE: "Bei Vereisung loesen sich Eisstuecke von den Rotorblaettern und werden durch die Fliehkraft weit geschleudert.",
|
ScenarioDE: "Bei Vereisung loesen sich Eisstuecke von den Rotorblaettern und werden durch die Fliehkraft weit geschleudert.",
|
||||||
TriggerDE: "Vereisung im Winter, fehlende Eiserkennungssysteme, Weiterbetrieb bei Eisansatz",
|
TriggerDE: "Vereisung im Winter, fehlende Eiserkennungssysteme, Weiterbetrieb bei Eisansatz",
|
||||||
HarmDE: "Verletzung durch Eisschlag, Sachschaeden an Fahrzeugen und Gebaeuden",
|
HarmDE: "Verletzung durch Eisschlag, Sachschaeden an Fahrzeugen und Gebaeuden",
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func GetSpecificMachinePatterns2() []HazardPattern {
|
|||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M003", "M141"},
|
SuggestedMeasureIDs: []string{"M003", "M141"},
|
||||||
SuggestedEvidenceIDs: []string{"E08", "E20"},
|
SuggestedEvidenceIDs: []string{"E08", "E20"},
|
||||||
Priority: 80,
|
Priority: 80, MachineTypes: []string{"escalator"},
|
||||||
ScenarioDE: "Finger oder Handteile werden am Einzugspunkt des Handlaufs in die Verkleidung gezogen.",
|
ScenarioDE: "Finger oder Handteile werden am Einzugspunkt des Handlaufs in die Verkleidung gezogen.",
|
||||||
TriggerDE: "Kinderhand am Handlauf nahe der Verkleidung, fehlende Einlaufschutzbuegel",
|
TriggerDE: "Kinderhand am Handlauf nahe der Verkleidung, fehlende Einlaufschutzbuegel",
|
||||||
HarmDE: "Fingerquetschung, Hautabschuerfungen, bei Kindern Armverletzung",
|
HarmDE: "Fingerquetschung, Hautabschuerfungen, bei Kindern Armverletzung",
|
||||||
@@ -39,7 +39,7 @@ func GetSpecificMachinePatterns2() []HazardPattern {
|
|||||||
DefaultSeverity: 3, DefaultExposure: 4,
|
DefaultSeverity: 3, DefaultExposure: 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "HP758", NameDE: "Sturz bei Notbremsung der Fahrtreppe", NameEN: "Fall during emergency stop of escalator",
|
ID: "HP758", MachineTypes: []string{"escalator", "elevator"}, NameDE: "Sturz bei Notbremsung der Fahrtreppe", NameEN: "Fall during emergency stop of escalator",
|
||||||
RequiredComponentTags: []string{"moving_part"},
|
RequiredComponentTags: []string{"moving_part"},
|
||||||
RequiredEnergyTags: []string{"kinetic"},
|
RequiredEnergyTags: []string{"kinetic"},
|
||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
@@ -75,7 +75,7 @@ func GetSpecificMachinePatterns2() []HazardPattern {
|
|||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M003", "M141"},
|
SuggestedMeasureIDs: []string{"M003", "M141"},
|
||||||
SuggestedEvidenceIDs: []string{"E08", "E09", "E20"},
|
SuggestedEvidenceIDs: []string{"E08", "E09", "E20"},
|
||||||
Priority: 85,
|
Priority: 85, MachineTypes: []string{"escalator", "elevator"},
|
||||||
ScenarioDE: "Bruch einer Trittstufe oder der Kammplatte fuehrt zum Einsacken oder Einzug in die Mechanik.",
|
ScenarioDE: "Bruch einer Trittstufe oder der Kammplatte fuehrt zum Einsacken oder Einzug in die Mechanik.",
|
||||||
TriggerDE: "Materialermuedung, Korrosion, fehlende Inspektionen, Vandalismus",
|
TriggerDE: "Materialermuedung, Korrosion, fehlende Inspektionen, Vandalismus",
|
||||||
HarmDE: "Einzug in Mechanik, Beinverletzungen, Sturz in Maschinenkammer",
|
HarmDE: "Einzug in Mechanik, Beinverletzungen, Sturz in Maschinenkammer",
|
||||||
@@ -173,7 +173,7 @@ func GetSpecificMachinePatterns2() []HazardPattern {
|
|||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M003", "M141"},
|
SuggestedMeasureIDs: []string{"M003", "M141"},
|
||||||
SuggestedEvidenceIDs: []string{"E01", "E20"},
|
SuggestedEvidenceIDs: []string{"E01", "E20"},
|
||||||
Priority: 95,
|
Priority: 95, MachineTypes: []string{"playground"},
|
||||||
ScenarioDE: "Kind steckt Kopf durch Oeffnung im Spielgeraet und bleibt haengen (Kopf-Entrapment-Gefahr bei 89-230 mm).",
|
ScenarioDE: "Kind steckt Kopf durch Oeffnung im Spielgeraet und bleibt haengen (Kopf-Entrapment-Gefahr bei 89-230 mm).",
|
||||||
TriggerDE: "Oeffnungen im kritischen Bereich 89-230 mm, V-foermige Spalte, Gelaendersprosse mit Kopffangmass",
|
TriggerDE: "Oeffnungen im kritischen Bereich 89-230 mm, V-foermige Spalte, Gelaendersprosse mit Kopffangmass",
|
||||||
HarmDE: "Strangulation, Erstickung, toedliche Verletzung",
|
HarmDE: "Strangulation, Erstickung, toedliche Verletzung",
|
||||||
@@ -233,7 +233,7 @@ func GetSpecificMachinePatterns2() []HazardPattern {
|
|||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M003", "M141"},
|
SuggestedMeasureIDs: []string{"M003", "M141"},
|
||||||
SuggestedEvidenceIDs: []string{"E01", "E20"},
|
SuggestedEvidenceIDs: []string{"E01", "E20"},
|
||||||
Priority: 95,
|
Priority: 95, MachineTypes: []string{"playground"},
|
||||||
ScenarioDE: "Kind verfaengt sich mit Kapuzenkordel, Schal oder Halskette in Seilen oder Netzen des Spielgeraets.",
|
ScenarioDE: "Kind verfaengt sich mit Kapuzenkordel, Schal oder Halskette in Seilen oder Netzen des Spielgeraets.",
|
||||||
TriggerDE: "Kleidung mit Kordeln am Hals, zu grosse Maschenweite, lose Seilenden",
|
TriggerDE: "Kleidung mit Kordeln am Hals, zu grosse Maschenweite, lose Seilenden",
|
||||||
HarmDE: "Strangulation, Erstickung, toedliche Verletzung",
|
HarmDE: "Strangulation, Erstickung, toedliche Verletzung",
|
||||||
@@ -361,7 +361,7 @@ func GetSpecificMachinePatterns2() []HazardPattern {
|
|||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M003", "M004", "M082"},
|
SuggestedMeasureIDs: []string{"M003", "M004", "M082"},
|
||||||
SuggestedEvidenceIDs: []string{"E08", "E09"},
|
SuggestedEvidenceIDs: []string{"E08", "E09"},
|
||||||
Priority: 85,
|
Priority: 85, MachineTypes: []string{"laundry"},
|
||||||
ScenarioDE: "Person greift in die drehende Trommel der Industriewaschmaschine und wird eingezogen.",
|
ScenarioDE: "Person greift in die drehende Trommel der Industriewaschmaschine und wird eingezogen.",
|
||||||
TriggerDE: "Defekte Tuerverriegelung, Oeffnen waehrend Nachlauf, Bedienfehler",
|
TriggerDE: "Defekte Tuerverriegelung, Oeffnen waehrend Nachlauf, Bedienfehler",
|
||||||
HarmDE: "Schwere Quetschverletzung, Armeinzug, Strangulation durch Waeschestuecke",
|
HarmDE: "Schwere Quetschverletzung, Armeinzug, Strangulation durch Waeschestuecke",
|
||||||
@@ -411,7 +411,7 @@ func GetSpecificMachinePatterns2() []HazardPattern {
|
|||||||
SuggestedMeasureIDs: []string{"M005", "M141"},
|
SuggestedMeasureIDs: []string{"M005", "M141"},
|
||||||
SuggestedEvidenceIDs: []string{"E20"},
|
SuggestedEvidenceIDs: []string{"E20"},
|
||||||
Priority: 80,
|
Priority: 80,
|
||||||
ScenarioDE: "Grosse Glasscheibe zerbricht beim Transport oder bei der Montage und trifft umstehende Personen.",
|
ScenarioDE: "Grosse Glasscheibe zerbricht oder durch mechanische Einwirkung und trifft umstehende Personen.",
|
||||||
TriggerDE: "Thermische Spannungen, mechanische Beschaedigung, fehlerhafter Saugnapp, Windlast",
|
TriggerDE: "Thermische Spannungen, mechanische Beschaedigung, fehlerhafter Saugnapp, Windlast",
|
||||||
HarmDE: "Tiefe Schnittwunden, Amputationsgefahr, toedliche Verletzung bei grossen Scheiben",
|
HarmDE: "Tiefe Schnittwunden, Amputationsgefahr, toedliche Verletzung bei grossen Scheiben",
|
||||||
AffectedDE: "Transportpersonal, Monteure, Passanten",
|
AffectedDE: "Transportpersonal, Monteure, Passanten",
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func GetTextileAgriPatterns() []HazardPattern {
|
|||||||
SuggestedMeasureIDs: []string{"M452", "M061"}, SuggestedEvidenceIDs: []string{"E01"},
|
SuggestedMeasureIDs: []string{"M452", "M061"}, SuggestedEvidenceIDs: []string{"E01"},
|
||||||
Priority: 78, MachineTypes: []string{"textile", "knitting"},
|
Priority: 78, MachineTypes: []string{"textile", "knitting"},
|
||||||
OperationalStates: []string{"automatic_operation", "maintenance"}, HumanRoles: []string{"operator", "maintenance_tech"},
|
OperationalStates: []string{"automatic_operation", "maintenance"}, HumanRoles: []string{"operator", "maintenance_tech"},
|
||||||
ScenarioDE: "Kontakt mit schnell bewegenden Nadeln bei Wartung oder Fadenwechsel",
|
ScenarioDE: "Kontakt mit schnell bewegenden Nadeln oder Fadenwechsel",
|
||||||
TriggerDE: "Eingriff in Nadelbereich bei laufender Maschine", HarmDE: "Stichverletzung, Schnittwunde",
|
TriggerDE: "Eingriff in Nadelbereich bei laufender Maschine", HarmDE: "Stichverletzung, Schnittwunde",
|
||||||
AffectedDE: "Bedienpersonal", ZoneDE: "Nadelbett",
|
AffectedDE: "Bedienpersonal", ZoneDE: "Nadelbett",
|
||||||
DefaultSeverity: 3, DefaultExposure: 4},
|
DefaultSeverity: 3, DefaultExposure: 4},
|
||||||
@@ -123,7 +123,7 @@ func GetTextileAgriPatterns() []HazardPattern {
|
|||||||
SuggestedMeasureIDs: []string{"M461", "M465"}, SuggestedEvidenceIDs: []string{"E01", "E08"},
|
SuggestedMeasureIDs: []string{"M461", "M465"}, SuggestedEvidenceIDs: []string{"E01", "E08"},
|
||||||
Priority: 94, MachineTypes: []string{"agricultural", "harvester", "combine"},
|
Priority: 94, MachineTypes: []string{"agricultural", "harvester", "combine"},
|
||||||
OperationalStates: []string{"automatic_operation", "maintenance"}, HumanRoles: []string{"operator", "maintenance_tech"},
|
OperationalStates: []string{"automatic_operation", "maintenance"}, HumanRoles: []string{"operator", "maintenance_tech"},
|
||||||
ScenarioDE: "Kontakt mit rotierendem Schneidwerk bei Wartung oder Blockierungsbeseitigung",
|
ScenarioDE: "Kontakt mit rotierendem Schneidwerk oder Blockierungsbeseitigung",
|
||||||
TriggerDE: "Maschine nicht abgestellt, hydraulischer Nachlauf",
|
TriggerDE: "Maschine nicht abgestellt, hydraulischer Nachlauf",
|
||||||
HarmDE: "Amputation, schwere Schnittverletzungen", AffectedDE: "Bediener, Wartungspersonal", ZoneDE: "Schneidwerksbereich",
|
HarmDE: "Amputation, schwere Schnittverletzungen", AffectedDE: "Bediener, Wartungspersonal", ZoneDE: "Schneidwerksbereich",
|
||||||
DefaultSeverity: 5, DefaultExposure: 3},
|
DefaultSeverity: 5, DefaultExposure: 3},
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func builtinThermalPatterns() []HazardPattern {
|
|||||||
SuggestedEvidenceIDs: []string{"E01"},
|
SuggestedEvidenceIDs: []string{"E01"},
|
||||||
Priority: 75,
|
Priority: 75,
|
||||||
ScenarioDE: "Aktuatoren (Servomotoren, Linearantriebe) erwaermen sich im Dauerbetrieb ueber die Beruehrtemperaturgrenze.",
|
ScenarioDE: "Aktuatoren (Servomotoren, Linearantriebe) erwaermen sich im Dauerbetrieb ueber die Beruehrtemperaturgrenze.",
|
||||||
TriggerDE: "Beruehren heisser Motorgehaeuse bei Wartung oder Stoerungsbeseitigung ohne ausreichende Abkuehlzeit.",
|
TriggerDE: "Beruehren heisser Motorgehaeuse ohne ausreichende Abkuehlzeit.",
|
||||||
HarmDE: "Kontaktverbrennung, Blasenbildung an Haenden.",
|
HarmDE: "Kontaktverbrennung, Blasenbildung an Haenden.",
|
||||||
AffectedDE: "Wartungspersonal, Einrichter",
|
AffectedDE: "Wartungspersonal, Einrichter",
|
||||||
ZoneDE: "Motorgehaeuse, Getriebegehaeuse, Linearantrieb",
|
ZoneDE: "Motorgehaeuse, Getriebegehaeuse, Linearantrieb",
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ func GetWeldingGlassTextilePatterns() []HazardPattern {
|
|||||||
GeneratedHazardCats: []string{"mechanical_hazard"},
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
SuggestedMeasureIDs: []string{"M003", "M004", "M082"},
|
SuggestedMeasureIDs: []string{"M003", "M004", "M082"},
|
||||||
SuggestedEvidenceIDs: []string{"E08", "E09"},
|
SuggestedEvidenceIDs: []string{"E08", "E09"},
|
||||||
Priority: 80,
|
Priority: 80, MachineTypes: []string{"glass_washing"},
|
||||||
ScenarioDE: "Transportwalzen der Glaswaschmaschine erfassen Finger oder Kleidung beim manuellen Einlegen der Scheiben.",
|
ScenarioDE: "Transportwalzen der Glaswaschmaschine erfassen Finger oder Kleidung beim manuellen Einlegen der Scheiben.",
|
||||||
TriggerDE: "Manuelles Nachjustieren bei laufenden Walzen, fehlender Schutz am Einlaufbereich",
|
TriggerDE: "Manuelles Nachjustieren bei laufenden Walzen, fehlender Schutz am Einlaufbereich",
|
||||||
HarmDE: "Fingerquetschung, Einzug der Hand, Hautabschaelungen",
|
HarmDE: "Fingerquetschung, Einzug der Hand, Hautabschaelungen",
|
||||||
|
|||||||
@@ -71,21 +71,21 @@ func getSupplementaryMeasures() []ProtectiveMeasureEntry {
|
|||||||
// Elektrische Sicherheit — Potentialausgleich & Ableitstroeme
|
// Elektrische Sicherheit — Potentialausgleich & Ableitstroeme
|
||||||
// Gap: GT-Benchmark 2.12 (Potentialausgleich), 2.4 (Ableitstroeme)
|
// Gap: GT-Benchmark 2.12 (Potentialausgleich), 2.4 (Ableitstroeme)
|
||||||
// ══════════════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════════════
|
||||||
{ID: "M410", ReductionType: "design", SubType: "electrical_safety", Name: "Potentialausgleich zwischen Anlagenteilen", Description: "Alle leitfaehigen Anlagenteile mit unterschiedlicher Energieversorgung werden ueber einen Potentialausgleichsleiter verbunden um gefaehrliche Beruehrungsspannungen zu vermeiden.", HazardCategory: "electrical", Examples: []string{"Potentialausgleich zwischen Roboterzelle und Werkzeugmaschine", "Potentialausgleichsschiene im Schaltschrank"}, NormReferences: []string{"IEC 60204-1 Ziff. 8.2", "IEC 61439-1"}},
|
{ID: "M475", ReductionType: "design", SubType: "electrical_safety", Name: "Potentialausgleich zwischen Anlagenteilen", Description: "Alle leitfaehigen Anlagenteile mit unterschiedlicher Energieversorgung werden ueber einen Potentialausgleichsleiter verbunden um gefaehrliche Beruehrungsspannungen zu vermeiden.", HazardCategory: "electrical", Examples: []string{"Potentialausgleich zwischen Roboterzelle und Werkzeugmaschine", "Potentialausgleichsschiene im Schaltschrank"}, NormReferences: []string{"IEC 60204-1 Ziff. 8.2", "IEC 61439-1"}},
|
||||||
{ID: "M411", ReductionType: "design", SubType: "electrical_safety", Name: "Schutz bei erhoehten Ableitstroemen", Description: "Bei Ableitstroemen ueber 10 mA wird der Schutzleiter mechanisch geschuetzt oder ein zusaetzlicher Schutzleiter verlegt und die Verbindung ueberwacht.", HazardCategory: "electrical", Examples: []string{"Schutzrohr fuer Schutzleiter an Frequenzumrichter", "Doppelter Schutzleiter mit Ueberwachung"}, NormReferences: []string{"IEC 60204-1 Ziff. 8.2.6"}},
|
{ID: "M476", ReductionType: "design", SubType: "electrical_safety", Name: "Schutz bei erhoehten Ableitstroemen", Description: "Bei Ableitstroemen ueber 10 mA wird der Schutzleiter mechanisch geschuetzt oder ein zusaetzlicher Schutzleiter verlegt und die Verbindung ueberwacht.", HazardCategory: "electrical", Examples: []string{"Schutzrohr fuer Schutzleiter an Frequenzumrichter", "Doppelter Schutzleiter mit Ueberwachung"}, NormReferences: []string{"IEC 60204-1 Ziff. 8.2.6"}},
|
||||||
{ID: "M412", ReductionType: "design", SubType: "electrical_safety", Name: "Dimensionierung von Luft- und Kriechstrecken", Description: "Luft- und Kriechstrecken werden entsprechend der elektrischen Beanspruchung und Verschmutzungsgrad dimensioniert um Kurzschluesse und gefaehrliche Beruehrungsspannungen zu vermeiden.", HazardCategory: "electrical", Examples: []string{"Mindestabstaende in Schaltgeraetekombinationen einhalten", "Isolationsueberwachung installieren"}, NormReferences: []string{"IEC 60204-1 Ziff. 6.2", "IEC 61439-1"}},
|
{ID: "M477", ReductionType: "design", SubType: "electrical_safety", Name: "Dimensionierung von Luft- und Kriechstrecken", Description: "Luft- und Kriechstrecken werden entsprechend der elektrischen Beanspruchung und Verschmutzungsgrad dimensioniert um Kurzschluesse und gefaehrliche Beruehrungsspannungen zu vermeiden.", HazardCategory: "electrical", Examples: []string{"Mindestabstaende in Schaltgeraetekombinationen einhalten", "Isolationsueberwachung installieren"}, NormReferences: []string{"IEC 60204-1 Ziff. 6.2", "IEC 61439-1"}},
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════════════
|
||||||
// EMV-Sicherheit
|
// EMV-Sicherheit
|
||||||
// Gap: GT-Benchmark 6.1 (EMV-Stoereinfluss auf Sicherheitsfunktionen)
|
// Gap: GT-Benchmark 6.1 (EMV-Stoereinfluss auf Sicherheitsfunktionen)
|
||||||
// ══════════════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════════════
|
||||||
{ID: "M415", ReductionType: "design", SubType: "emc_safety", Name: "EMV-konforme Installation und Verkabelung", Description: "Alle sicherheitsrelevanten Komponenten und Sub-Systeme werden nach EMV-Richtlinien installiert und verkabelt um Stoereinfluss auf Sicherheitsfunktionen zu verhindern.", HazardCategory: "electrical", Examples: []string{"Geschirmte Steuerleitungen verwenden", "Getrennte Kabelkanaele fuer Leistungs- und Signalleitungen"}, NormReferences: []string{"IEC 61000-6-2", "EN 16090-1 Ziff. 5.8.7"}},
|
{ID: "M478", ReductionType: "design", SubType: "emc_safety", Name: "EMV-konforme Installation und Verkabelung", Description: "Alle sicherheitsrelevanten Komponenten und Sub-Systeme werden nach EMV-Richtlinien installiert und verkabelt um Stoereinfluss auf Sicherheitsfunktionen zu verhindern.", HazardCategory: "electrical", Examples: []string{"Geschirmte Steuerleitungen verwenden", "Getrennte Kabelkanaele fuer Leistungs- und Signalleitungen"}, NormReferences: []string{"IEC 61000-6-2", "EN 16090-1 Ziff. 5.8.7"}},
|
||||||
{ID: "M416", ReductionType: "design", SubType: "emc_safety", Name: "EMV-Pruefung sicherheitsrelevanter Systeme", Description: "Sicherheitsrelevante Steuerungen und Antriebe werden auf Stoerfestigkeit gegenueber elektromagnetischen Einflussgroessen geprueft.", HazardCategory: "electrical", Examples: []string{"Burst/Surge-Pruefung nach IEC 61000-4", "Stoerfestigkeitspruefung der Sicherheits-SPS"}, NormReferences: []string{"IEC 61000-4-4", "IEC 61000-4-5", "IEC 62061"}},
|
{ID: "M479", ReductionType: "design", SubType: "emc_safety", Name: "EMV-Pruefung sicherheitsrelevanter Systeme", Description: "Sicherheitsrelevante Steuerungen und Antriebe werden auf Stoerfestigkeit gegenueber elektromagnetischen Einflussgroessen geprueft.", HazardCategory: "electrical", Examples: []string{"Burst/Surge-Pruefung nach IEC 61000-4", "Stoerfestigkeitspruefung der Sicherheits-SPS"}, NormReferences: []string{"IEC 61000-4-4", "IEC 61000-4-5", "IEC 62061"}},
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════════════
|
||||||
// Kuehlschmierstoff-Leitungssicherheit
|
// Kuehlschmierstoff-Leitungssicherheit
|
||||||
// Gap: GT-Benchmark 2.10 (KSS-Leckage fuehrt zu Brand)
|
// Gap: GT-Benchmark 2.10 (KSS-Leckage fuehrt zu Brand)
|
||||||
// ══════════════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════════════
|
||||||
{ID: "M420", ReductionType: "design", SubType: "fluid_safety", Name: "Druckfeste Auslegung von KSS-Leitungen", Description: "Schlaeuche, Dichtungen, Verbindungsstuecke und Befestigungen des Kuehlschmierstoffsystems werden auf den Nenndruck der jeweiligen Komponente ausgelegt und gegen Abspringen gesichert.", HazardCategory: "mechanical", Examples: []string{"Druckschlaeuche auf maximalen Betriebsdruck dimensionieren", "Schlauchbruchsicherungen an kritischen Verbindungen"}, NormReferences: []string{"IEC 60204-1 Ziff. 11.3", "EN ISO 4414"}},
|
{ID: "M480", ReductionType: "design", SubType: "fluid_safety", Name: "Druckfeste Auslegung von KSS-Leitungen", Description: "Schlaeuche, Dichtungen, Verbindungsstuecke und Befestigungen des Kuehlschmierstoffsystems werden auf den Nenndruck der jeweiligen Komponente ausgelegt und gegen Abspringen gesichert.", HazardCategory: "mechanical", Examples: []string{"Druckschlaeuche auf maximalen Betriebsdruck dimensionieren", "Schlauchbruchsicherungen an kritischen Verbindungen"}, NormReferences: []string{"IEC 60204-1 Ziff. 11.3", "EN ISO 4414"}},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package iace
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// TestHP1640_ResolvesToContactProtection pins the GT-2.2 fix: the "direct
|
||||||
|
// contact with live parts" pattern must resolve to electrical-contact-protection
|
||||||
|
// measures (basic protection, double insulation, earthing, equipotential
|
||||||
|
// bonding), not to mechanical fallbacks like chip extraction.
|
||||||
|
func TestHP1640_ResolvesToContactProtection(t *testing.T) {
|
||||||
|
measureByID := make(map[string]ProtectiveMeasureEntry)
|
||||||
|
for _, m := range GetProtectiveMeasureLibrary() {
|
||||||
|
measureByID[m.ID] = m
|
||||||
|
}
|
||||||
|
|
||||||
|
patterns := GetRobotCellPatterns()
|
||||||
|
var hp1640 *HazardPattern
|
||||||
|
for i := range patterns {
|
||||||
|
if patterns[i].ID == "HP1640" {
|
||||||
|
hp1640 = &patterns[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hp1640 == nil {
|
||||||
|
t.Fatal("HP1640 not found in robot cell patterns")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(hp1640.SuggestedMeasureIDs) < 3 {
|
||||||
|
t.Errorf("HP1640 should suggest at least 3 measures, got %d", len(hp1640.SuggestedMeasureIDs))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, mid := range hp1640.SuggestedMeasureIDs {
|
||||||
|
m, ok := measureByID[mid]
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("HP1640 references non-existent measure %s", mid)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if m.HazardCategory != "electrical" {
|
||||||
|
t.Errorf("HP1640 measure %s (%q) has HazardCategory=%s, expected electrical",
|
||||||
|
mid, m.Name, m.HazardCategory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHP1688_M475IsPotentialausgleich pins the M475 rename: HP1688 (touch
|
||||||
|
// voltage from potential differences) must resolve M475 to the equipotential
|
||||||
|
// bonding measure, not to the metalworking chip extraction that previously
|
||||||
|
// occupied M410 and overwrote the electrical definition.
|
||||||
|
func TestHP1688_M475IsPotentialausgleich(t *testing.T) {
|
||||||
|
measureByID := make(map[string]ProtectiveMeasureEntry)
|
||||||
|
for _, m := range GetProtectiveMeasureLibrary() {
|
||||||
|
measureByID[m.ID] = m
|
||||||
|
}
|
||||||
|
|
||||||
|
m, ok := measureByID["M475"]
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("M475 not defined — supplementary rename did not land")
|
||||||
|
}
|
||||||
|
if m.HazardCategory != "electrical" {
|
||||||
|
t.Errorf("M475 must be HazardCategory=electrical, got %s (%q)", m.HazardCategory, m.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
patterns := GetRobotCellPatternsExt()
|
||||||
|
var hp1688 *HazardPattern
|
||||||
|
for i := range patterns {
|
||||||
|
if patterns[i].ID == "HP1688" {
|
||||||
|
hp1688 = &patterns[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hp1688 == nil {
|
||||||
|
t.Fatal("HP1688 not found in robot cell ext patterns")
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, mid := range hp1688.SuggestedMeasureIDs {
|
||||||
|
if mid == "M475" {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("HP1688 must reference M475 (Potentialausgleich), got %v", hp1688.SuggestedMeasureIDs)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,6 +66,8 @@ type PatternMatch struct {
|
|||||||
HumanRoles []string `json:"human_roles,omitempty"`
|
HumanRoles []string `json:"human_roles,omitempty"`
|
||||||
GeneratedHazardType string `json:"generated_hazard_type,omitempty"`
|
GeneratedHazardType string `json:"generated_hazard_type,omitempty"`
|
||||||
MatchedFailureModes []string `json:"matched_failure_modes,omitempty"`
|
MatchedFailureModes []string `json:"matched_failure_modes,omitempty"`
|
||||||
|
ApplicableLifecycles []string `json:"applicable_lifecycles,omitempty"`
|
||||||
|
SuggestedMeasureIDs []string `json:"suggested_measure_ids,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// HazardSuggestion is a suggested hazard from pattern matching.
|
// HazardSuggestion is a suggested hazard from pattern matching.
|
||||||
@@ -217,7 +219,9 @@ func (e *PatternEngine) Match(input MatchInput) *MatchOutput {
|
|||||||
StateTransitions: p.StateTransitions,
|
StateTransitions: p.StateTransitions,
|
||||||
HumanRoles: p.HumanRoles,
|
HumanRoles: p.HumanRoles,
|
||||||
GeneratedHazardType: p.GeneratedHazardType,
|
GeneratedHazardType: p.GeneratedHazardType,
|
||||||
MatchedFailureModes: matchedFMs,
|
MatchedFailureModes: matchedFMs,
|
||||||
|
ApplicableLifecycles: p.ApplicableLifecycles,
|
||||||
|
SuggestedMeasureIDs: p.SuggestedMeasureIDs,
|
||||||
})
|
})
|
||||||
|
|
||||||
for _, cat := range p.GeneratedHazardCats {
|
for _, cat := range p.GeneratedHazardCats {
|
||||||
|
|||||||
@@ -38,5 +38,6 @@ func collectAllPatterns() []HazardPattern {
|
|||||||
patterns = append(patterns, GetVDMAIndustryPatterns()...) // HP1500-HP1549 VDMA sectors (Phase 3)
|
patterns = append(patterns, GetVDMAIndustryPatterns()...) // HP1500-HP1549 VDMA sectors (Phase 3)
|
||||||
patterns = append(patterns, GetTextileAgriPatterns()...) // HP1550-HP1584 Textile + Agri (Phase 5)
|
patterns = append(patterns, GetTextileAgriPatterns()...) // HP1550-HP1584 Textile + Agri (Phase 5)
|
||||||
patterns = append(patterns, GetRobotCellPatterns()...) // HP1600-HP1649 Robot cell (GT benchmark)
|
patterns = append(patterns, GetRobotCellPatterns()...) // HP1600-HP1649 Robot cell (GT benchmark)
|
||||||
|
patterns = append(patterns, GetRobotCellPatternsExt()...) // HP1650-HP1699 Robot cell extended (GT gaps)
|
||||||
return patterns
|
return patterns
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,12 +64,16 @@ class ComplianceCheckStatusResponse(BaseModel):
|
|||||||
|
|
||||||
@router.post("/extract-text")
|
@router.post("/extract-text")
|
||||||
async def extract_text(req: ExtractTextRequest):
|
async def extract_text(req: ExtractTextRequest):
|
||||||
"""Extract text from a URL via consent-tester DSI discovery."""
|
"""Extract text from a URL via consent-tester DSI discovery.
|
||||||
|
|
||||||
|
Merges all documents found on the page (sub-pages, accordions, etc.)
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(timeout=90.0) as client:
|
async with httpx.AsyncClient(timeout=300.0) as client:
|
||||||
resp = await client.post(
|
resp = await client.post(
|
||||||
f"{CONSENT_TESTER_URL}/dsi-discovery",
|
f"{CONSENT_TESTER_URL}/dsi-discovery",
|
||||||
json={"url": req.url, "max_documents": 1},
|
json={"url": req.url, "max_documents": 5},
|
||||||
|
timeout=300.0,
|
||||||
)
|
)
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
return {
|
return {
|
||||||
@@ -86,10 +90,15 @@ async def extract_text(req: ExtractTextRequest):
|
|||||||
"error": "Kein Text extrahierbar",
|
"error": "Kein Text extrahierbar",
|
||||||
}
|
}
|
||||||
|
|
||||||
doc = docs[0]
|
# Merge all documents (handles multi-page DSIs like BMW)
|
||||||
text = doc.get("full_text", "") or doc.get("text_preview", "") or doc.get("text", "")
|
texts = []
|
||||||
title = doc.get("title", "") or doc.get("doc_type", "")
|
for doc in docs:
|
||||||
word_count = doc.get("word_count", 0) or len(text.split())
|
t = doc.get("full_text", "") or doc.get("text_preview", "") or ""
|
||||||
|
if t and len(t) > 50:
|
||||||
|
texts.append(t)
|
||||||
|
text = "\n\n".join(texts) if texts else ""
|
||||||
|
title = docs[0].get("title", "") or docs[0].get("doc_type", "")
|
||||||
|
word_count = len(text.split())
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"text": text,
|
"text": text,
|
||||||
@@ -178,11 +187,16 @@ async def _run_compliance_check(check_id: str, req: ComplianceCheckRequest):
|
|||||||
# 1. Same URL used for multiple doc_types → split by heading
|
# 1. Same URL used for multiple doc_types → split by heading
|
||||||
# 2. DSI text contains Cookie/Social-Media sections → auto-fill empty rows
|
# 2. DSI text contains Cookie/Social-Media sections → auto-fill empty rows
|
||||||
from compliance.services.section_splitter import (
|
from compliance.services.section_splitter import (
|
||||||
split_shared_texts, auto_fill_from_dsi,
|
split_shared_texts, auto_fill_from_dsi, cross_search_documents,
|
||||||
)
|
)
|
||||||
split_shared_texts(doc_entries, url_text_cache)
|
split_shared_texts(doc_entries, url_text_cache)
|
||||||
auto_fill_from_dsi(doc_entries)
|
auto_fill_from_dsi(doc_entries)
|
||||||
# Refresh doc_texts after splitting
|
|
||||||
|
# Step 1c: Cross-document search — find doc_types in wrong documents
|
||||||
|
_update(check_id, "Dokumente werden uebergreifend durchsucht...")
|
||||||
|
placement_findings = cross_search_documents(doc_entries)
|
||||||
|
|
||||||
|
# Refresh doc_texts after all splitting/searching
|
||||||
for entry in doc_entries:
|
for entry in doc_entries:
|
||||||
if entry.get("text"):
|
if entry.get("text"):
|
||||||
doc_texts[entry["doc_type"]] = entry["text"]
|
doc_texts[entry["doc_type"]] = entry["text"]
|
||||||
@@ -232,6 +246,16 @@ async def _run_compliance_check(check_id: str, req: ComplianceCheckRequest):
|
|||||||
# Apply profile context filter
|
# Apply profile context filter
|
||||||
result = _apply_profile_filter(result, profile, doc_type)
|
result = _apply_profile_filter(result, profile, doc_type)
|
||||||
|
|
||||||
|
# Add placement findings — but only if the regex checks confirm
|
||||||
|
# the text doesn't match. If completeness >= 50%, the text IS the
|
||||||
|
# right doc_type despite missing cross-search keywords.
|
||||||
|
if result.completeness_pct < 50:
|
||||||
|
for pf in placement_findings:
|
||||||
|
if pf.get("doc_type") == doc_type:
|
||||||
|
result.checks.insert(0, CheckItem(**{
|
||||||
|
k: v for k, v in pf.items() if k != "doc_type"
|
||||||
|
}))
|
||||||
|
|
||||||
results.append(result)
|
results.append(result)
|
||||||
total_findings += result.findings_count
|
total_findings += result.findings_count
|
||||||
|
|
||||||
@@ -302,17 +326,24 @@ async def _run_compliance_check(check_id: str, req: ComplianceCheckRequest):
|
|||||||
else:
|
else:
|
||||||
r.scenario = "import"
|
r.scenario = "import"
|
||||||
|
|
||||||
# Step 5: Build report
|
# Step 5: Build report with management summary
|
||||||
_update(check_id, "Report wird erstellt...")
|
_update(check_id, "Report wird erstellt...")
|
||||||
|
from .agent_doc_check_report import build_management_summary
|
||||||
|
summary_html = build_management_summary(results)
|
||||||
report_html = build_html_report(results, None)
|
report_html = build_html_report(results, None)
|
||||||
profile_html = _build_profile_html(profile)
|
profile_html = _build_profile_html(profile)
|
||||||
full_html = profile_html + report_html
|
full_html = summary_html + profile_html + report_html
|
||||||
|
|
||||||
# Step 6: Send email
|
# Step 6: Send email — include website/company name in subject
|
||||||
doc_count = len([r for r in results if not r.error])
|
doc_count = len([r for r in results if not r.error])
|
||||||
|
site_name = (
|
||||||
|
extracted_profile.get("company_profile", {}).get("companyName")
|
||||||
|
or _extract_domain(doc_entries)
|
||||||
|
or "Unbekannt"
|
||||||
|
)
|
||||||
email_result = send_email(
|
email_result = send_email(
|
||||||
recipient=req.recipient,
|
recipient=req.recipient,
|
||||||
subject=f"[COMPLIANCE-CHECK] {doc_count} Dokumente geprueft",
|
subject=f"[COMPLIANCE-CHECK] {site_name} — {doc_count} Dokumente geprueft",
|
||||||
body_html=full_html,
|
body_html=full_html,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -349,23 +380,55 @@ def _update(check_id: str, msg: str):
|
|||||||
|
|
||||||
|
|
||||||
async def _fetch_text(url: str) -> str:
|
async def _fetch_text(url: str) -> str:
|
||||||
"""Fetch text from URL via consent-tester."""
|
"""Fetch text from URL via consent-tester, with HTTP fallback.
|
||||||
|
|
||||||
|
1. Try consent-tester (Playwright) — handles JS-heavy SPAs
|
||||||
|
2. Fallback: direct HTTP fetch + HTML strip — fast, works for SSR pages
|
||||||
|
"""
|
||||||
|
# 1. Consent-tester (Playwright-based, full JS rendering)
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(timeout=90.0) as client:
|
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||||
resp = await client.post(
|
resp = await client.post(
|
||||||
f"{CONSENT_TESTER_URL}/dsi-discovery",
|
f"{CONSENT_TESTER_URL}/dsi-discovery",
|
||||||
json={"url": url, "max_documents": 1},
|
json={"url": url, "max_documents": 3},
|
||||||
|
timeout=60.0,
|
||||||
)
|
)
|
||||||
if resp.status_code != 200:
|
if resp.status_code == 200:
|
||||||
return ""
|
docs = resp.json().get("documents", [])
|
||||||
docs = resp.json().get("documents", [])
|
if docs:
|
||||||
if not docs:
|
texts = []
|
||||||
return ""
|
for doc in docs:
|
||||||
doc = docs[0]
|
t = doc.get("full_text", "") or doc.get("text_preview", "") or ""
|
||||||
return doc.get("full_text", "") or doc.get("text_preview", "") or ""
|
if t and len(t) > 50:
|
||||||
|
texts.append(t)
|
||||||
|
merged = "\n\n".join(texts)
|
||||||
|
if merged and len(merged.split()) > 100:
|
||||||
|
if len(texts) > 1:
|
||||||
|
logger.info("Merged %d docs from %s (%d words)",
|
||||||
|
len(texts), url, len(merged.split()))
|
||||||
|
return merged
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("Text fetch failed for %s: %s", url, e)
|
logger.warning("Consent-tester fetch failed for %s: %s", url, e)
|
||||||
return ""
|
|
||||||
|
# 2. Fallback: direct HTTP fetch (works for SSR pages like BMW)
|
||||||
|
try:
|
||||||
|
import re as _re
|
||||||
|
async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:
|
||||||
|
resp = await client.get(url)
|
||||||
|
if resp.status_code == 200 and "text/html" in resp.headers.get("content-type", ""):
|
||||||
|
html = resp.text
|
||||||
|
# Strip HTML tags, decode entities
|
||||||
|
text = _re.sub(r"<script[^>]*>.*?</script>", " ", html, flags=_re.DOTALL | _re.IGNORECASE)
|
||||||
|
text = _re.sub(r"<style[^>]*>.*?</style>", " ", text, flags=_re.DOTALL | _re.IGNORECASE)
|
||||||
|
text = _re.sub(r"<[^>]+>", " ", text)
|
||||||
|
text = _re.sub(r"\s+", " ", text).strip()
|
||||||
|
if len(text.split()) > 100:
|
||||||
|
logger.info("HTTP fallback for %s: %d words", url, len(text.split()))
|
||||||
|
return text
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("HTTP fallback failed for %s: %s", url, e)
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
async def _check_single(
|
async def _check_single(
|
||||||
@@ -440,6 +503,17 @@ async def _check_single(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_domain(doc_entries: list[dict]) -> str | None:
|
||||||
|
"""Extract domain name from first URL for email subject."""
|
||||||
|
for entry in doc_entries:
|
||||||
|
url = entry.get("url", "")
|
||||||
|
if url and "://" in url:
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
host = urlparse(url).netloc
|
||||||
|
return host.replace("www.", "") if host else None
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _get_skip_types(profile) -> dict[str, str]:
|
def _get_skip_types(profile) -> dict[str, str]:
|
||||||
"""Doc_types to skip entirely. Currently empty — we check everything
|
"""Doc_types to skip entirely. Currently empty — we check everything
|
||||||
and flag irrelevant items as INFO instead of skipping."""
|
and flag irrelevant items as INFO instead of skipping."""
|
||||||
|
|||||||
@@ -40,6 +40,121 @@ def _hint_box(hint: str) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_management_summary(results: list[DocCheckResult]) -> str:
|
||||||
|
"""Build a plain-language management summary for the CEO/GF.
|
||||||
|
|
||||||
|
No legal jargon — concrete actions that can be delegated to staff,
|
||||||
|
lawyers, or the DPO.
|
||||||
|
"""
|
||||||
|
ok = [r for r in results if r.completeness_pct == 100 and not r.error]
|
||||||
|
fixable = [r for r in results if 0 < r.completeness_pct < 100 and not r.error]
|
||||||
|
critical = [r for r in results if r.completeness_pct == 0 and not r.error]
|
||||||
|
errors = [r for r in results if r.error]
|
||||||
|
|
||||||
|
html = [
|
||||||
|
'<div style="font-family:-apple-system,BlinkMacSystemFont,sans-serif;'
|
||||||
|
'max-width:700px;margin:0 auto 20px;padding:16px 20px;'
|
||||||
|
'background:#f8fafc;border:1px solid #e2e8f0;border-radius:12px">',
|
||||||
|
'<h2 style="margin:0 0 12px;font-size:18px;color:#1e293b">'
|
||||||
|
'Zusammenfassung fuer die Geschaeftsfuehrung</h2>',
|
||||||
|
]
|
||||||
|
|
||||||
|
# Overall status
|
||||||
|
total = len(results) - len(errors)
|
||||||
|
if total == 0:
|
||||||
|
html.append('<p>Keine Dokumente geprueft.</p></div>')
|
||||||
|
return "\n".join(html)
|
||||||
|
|
||||||
|
if len(ok) == total:
|
||||||
|
html.append(
|
||||||
|
'<p style="color:#16a34a;font-weight:600;font-size:15px">'
|
||||||
|
'Alle Dokumente sind vollstaendig. Keine dringenden Massnahmen noetig.</p>'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
html.append(
|
||||||
|
f'<p style="font-size:14px;color:#475569">'
|
||||||
|
f'{len(ok)} von {total} Dokumenten sind vollstaendig. '
|
||||||
|
f'{len(fixable)} brauchen Korrekturen'
|
||||||
|
f'{f", {len(critical)} fehlen oder sind unbrauchbar" if critical else ""}.</p>'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Concrete actions
|
||||||
|
actions: list[str] = []
|
||||||
|
for r in results:
|
||||||
|
if r.error or r.completeness_pct == 100:
|
||||||
|
continue
|
||||||
|
failed_checks = [
|
||||||
|
c for c in r.checks
|
||||||
|
if c.level == 1 and not c.passed and not c.skipped
|
||||||
|
and c.severity != "INFO"
|
||||||
|
]
|
||||||
|
for c in failed_checks[:3]: # Max 3 per document
|
||||||
|
action = _check_to_action(r.label, c.label, c.hint)
|
||||||
|
if action:
|
||||||
|
actions.append(action)
|
||||||
|
|
||||||
|
if actions:
|
||||||
|
html.append(
|
||||||
|
'<h3 style="font-size:14px;color:#334155;margin:16px 0 8px">'
|
||||||
|
'Konkrete Aufgaben:</h3>'
|
||||||
|
'<ol style="font-size:13px;color:#475569;padding-left:20px;margin:0">'
|
||||||
|
)
|
||||||
|
for a in actions[:10]: # Max 10 actions
|
||||||
|
html.append(f'<li style="margin-bottom:6px">{a}</li>')
|
||||||
|
html.append('</ol>')
|
||||||
|
|
||||||
|
html.append('</div>')
|
||||||
|
return "\n".join(html)
|
||||||
|
|
||||||
|
|
||||||
|
def _check_to_action(doc_label: str, check_label: str, hint: str) -> str:
|
||||||
|
"""Convert a failed check into a plain-language action item."""
|
||||||
|
# Map technical check labels to business-language actions
|
||||||
|
label_lower = check_label.lower()
|
||||||
|
|
||||||
|
if "datenschutzbeauftragter" in label_lower or "dsb" in label_lower:
|
||||||
|
return (f"<strong>{doc_label}:</strong> Ihren Datenschutzbeauftragten "
|
||||||
|
f"mit Kontaktdaten erwaehnen. Pflicht ab 20 Mitarbeitern.")
|
||||||
|
|
||||||
|
if "beschwerderecht" in label_lower or "art. 77" in label_lower:
|
||||||
|
return (f"<strong>{doc_label}:</strong> Hinweis auf das Beschwerderecht "
|
||||||
|
f"bei der Aufsichtsbehoerde ergaenzen (Name + Kontakt der Behoerde).")
|
||||||
|
|
||||||
|
if "betroffenenrechte" in label_lower:
|
||||||
|
return (f"<strong>{doc_label}:</strong> Alle Betroffenenrechte "
|
||||||
|
f"(Auskunft, Berichtigung, Loeschung, etc.) einzeln auffuehren.")
|
||||||
|
|
||||||
|
if "verantwortlicher" in label_lower:
|
||||||
|
return (f"<strong>{doc_label}:</strong> Vollstaendige Firmenbezeichnung "
|
||||||
|
f"mit Rechtsform, Adresse, E-Mail und Telefon eintragen.")
|
||||||
|
|
||||||
|
if "interessenabwaegung" in label_lower:
|
||||||
|
return (f"<strong>{doc_label}:</strong> Bei 'berechtigtem Interesse' "
|
||||||
|
f"die Abwaegung dokumentieren. Aufgabe fuer den DSB/Rechtsanwalt.")
|
||||||
|
|
||||||
|
if "widerrufsbelehrung" in label_lower or "widerruf" in label_lower:
|
||||||
|
return (f"<strong>{doc_label}:</strong> Gesetzliche Widerrufsbelehrung "
|
||||||
|
f"mit 14-Tage-Frist und Musterformular bereitstellen.")
|
||||||
|
|
||||||
|
if "loeschkonzept" in label_lower:
|
||||||
|
return (f"<strong>{doc_label}:</strong> Loeschfristen und -prozess "
|
||||||
|
f"dokumentieren. Aufgabe fuer den DSB.")
|
||||||
|
|
||||||
|
if "profiling" in label_lower or "art. 22" in label_lower:
|
||||||
|
return (f"<strong>{doc_label}:</strong> Hinweis ergaenzen ob "
|
||||||
|
f"automatisierte Entscheidungen stattfinden oder nicht.")
|
||||||
|
|
||||||
|
if "nicht im eingereichten text" in label_lower:
|
||||||
|
return (f"<strong>{doc_label}:</strong> Das eingereichte Dokument "
|
||||||
|
f"enthaelt nicht den erwarteten Inhalt. Bitte korrekte URL pruefen.")
|
||||||
|
|
||||||
|
# Generic fallback
|
||||||
|
if hint and len(hint) < 150:
|
||||||
|
return f"<strong>{doc_label}:</strong> {hint[:120]}"
|
||||||
|
|
||||||
|
return f"<strong>{doc_label}:</strong> '{check_label}' muss ergaenzt werden."
|
||||||
|
|
||||||
|
|
||||||
def build_html_report(
|
def build_html_report(
|
||||||
results: list[DocCheckResult],
|
results: list[DocCheckResult],
|
||||||
cookie_result: dict | None,
|
cookie_result: dict | None,
|
||||||
|
|||||||
@@ -213,3 +213,197 @@ def auto_fill_from_dsi(doc_entries: list[dict]) -> None:
|
|||||||
"Auto-filled %d empty rows from DSI sections: %s",
|
"Auto-filled %d empty rows from DSI sections: %s",
|
||||||
len(filled), ", ".join(filled),
|
len(filled), ", ".join(filled),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Cross-Document Search ────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Keywords that indicate a doc_type is present in text (case-insensitive)
|
||||||
|
_DOC_TYPE_KEYWORDS = {
|
||||||
|
"widerruf": [
|
||||||
|
"widerrufsrecht", "widerrufsbelehrung", "widerrufsfrist",
|
||||||
|
"binnen 14 tagen", "widerruf erklaeren", "muster-widerrufsformular",
|
||||||
|
],
|
||||||
|
"cookie": [
|
||||||
|
"cookie-richtlinie", "cookie-tabelle", "cookiebot", "consent-tool",
|
||||||
|
"arten der cookies", "session-cookie", "tracking-cookie",
|
||||||
|
],
|
||||||
|
"social_media": [
|
||||||
|
"gemeinsam verantwortlich", "art. 26 dsgvo", "fanpage",
|
||||||
|
"social media plugin", "facebook-seite", "instagram-profil",
|
||||||
|
],
|
||||||
|
"impressum": [
|
||||||
|
"angaben gemaess", "angaben gemäß", "§ 5 tmg", "§5 tmg",
|
||||||
|
"telemediengesetz", "impressum",
|
||||||
|
],
|
||||||
|
"agb": [
|
||||||
|
"allgemeine geschaeftsbedingungen", "allgemeine geschäftsbedingungen",
|
||||||
|
"geltungsbereich", "vertragsschluss", "§305 bgb",
|
||||||
|
],
|
||||||
|
"dsb": [
|
||||||
|
"datenschutzbeauftragte", "dsb@", "dpo@",
|
||||||
|
"datenschutzbeauftragten",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def cross_search_documents(doc_entries: list[dict]) -> list[dict]:
|
||||||
|
"""Search ALL texts for ALL doc_types and fill missing entries.
|
||||||
|
|
||||||
|
For each empty doc_type row, search through all other documents'
|
||||||
|
texts to find the content. If found in the wrong document, extract
|
||||||
|
it, assign it, and create a finding about incorrect placement.
|
||||||
|
|
||||||
|
Returns list of findings (misplacement warnings).
|
||||||
|
"""
|
||||||
|
findings: list[dict] = []
|
||||||
|
|
||||||
|
# Collect all available texts with their source doc_type
|
||||||
|
all_texts: list[tuple[str, str, str]] = [] # (doc_type, url, text)
|
||||||
|
for entry in doc_entries:
|
||||||
|
if entry.get("text") and len(entry["text"]) > 100:
|
||||||
|
all_texts.append((entry["doc_type"], entry.get("url", ""), entry["text"]))
|
||||||
|
|
||||||
|
if not all_texts:
|
||||||
|
return findings
|
||||||
|
|
||||||
|
# For each entry, check if:
|
||||||
|
# a) It's empty → search other texts
|
||||||
|
# b) It has text but the text doesn't match the doc_type → search other texts
|
||||||
|
for entry in doc_entries:
|
||||||
|
target_type = entry["doc_type"]
|
||||||
|
keywords = _DOC_TYPE_KEYWORDS.get(target_type, [])
|
||||||
|
if not keywords:
|
||||||
|
continue
|
||||||
|
|
||||||
|
has_text = entry.get("text") and len(entry["text"].split()) > 50
|
||||||
|
text_matches = False
|
||||||
|
if has_text:
|
||||||
|
# Check if the current text actually contains this doc_type's content
|
||||||
|
entry_lower = entry["text"].lower()
|
||||||
|
match_score = sum(1 for kw in keywords if kw in entry_lower)
|
||||||
|
text_matches = match_score >= 2
|
||||||
|
|
||||||
|
if has_text and text_matches:
|
||||||
|
continue # Text present AND matches doc_type → skip
|
||||||
|
|
||||||
|
# Search all other texts for this doc_type's keywords
|
||||||
|
best_match: dict | None = None
|
||||||
|
best_score = 0
|
||||||
|
|
||||||
|
for source_type, source_url, source_text in all_texts:
|
||||||
|
if source_type == target_type:
|
||||||
|
continue
|
||||||
|
|
||||||
|
text_lower = source_text.lower()
|
||||||
|
score = sum(1 for kw in keywords if kw in text_lower)
|
||||||
|
|
||||||
|
if score >= 2 and score > best_score:
|
||||||
|
best_score = score
|
||||||
|
# Extract the relevant section
|
||||||
|
section = _extract_section_by_keywords(source_text, keywords)
|
||||||
|
if section and len(section.split()) >= 30:
|
||||||
|
best_match = {
|
||||||
|
"source_type": source_type,
|
||||||
|
"source_url": source_url,
|
||||||
|
"section_text": section,
|
||||||
|
"keyword_hits": score,
|
||||||
|
}
|
||||||
|
|
||||||
|
if best_match:
|
||||||
|
entry["text"] = best_match["section_text"]
|
||||||
|
entry["word_count"] = len(best_match["section_text"].split())
|
||||||
|
source_label = best_match["source_type"].upper()
|
||||||
|
entry["url"] = f"(gefunden in {source_label})"
|
||||||
|
|
||||||
|
findings.append({
|
||||||
|
"id": f"placement-{target_type}",
|
||||||
|
"label": f"{_type_label(target_type)} in falschem Dokument",
|
||||||
|
"passed": False,
|
||||||
|
"severity": "MEDIUM",
|
||||||
|
"level": 1,
|
||||||
|
"parent": None,
|
||||||
|
"skipped": False,
|
||||||
|
"matched_text": "",
|
||||||
|
"hint": (
|
||||||
|
f"Die {_type_label(target_type)} wurde nicht als eigenes "
|
||||||
|
f"Dokument gefunden, sondern in der/den {source_label} "
|
||||||
|
f"({best_match['source_url']}). Gemaess Art. 246a EGBGB / "
|
||||||
|
f"§312d BGB muss die {_type_label(target_type)} leicht "
|
||||||
|
f"auffindbar und klar erkennbar sein. Empfehlung: Als "
|
||||||
|
f"eigenen Link im Footer oder als separates Dokument "
|
||||||
|
f"bereitstellen."
|
||||||
|
),
|
||||||
|
"source": "cross_document_search",
|
||||||
|
"doc_type": target_type,
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"Cross-doc: Found %s in %s (%d keywords, %d words)",
|
||||||
|
target_type, best_match["source_type"],
|
||||||
|
best_match["keyword_hits"],
|
||||||
|
entry["word_count"],
|
||||||
|
)
|
||||||
|
elif has_text and not text_matches:
|
||||||
|
# Text present but doesn't match — wrong text assigned
|
||||||
|
findings.append({
|
||||||
|
"id": f"wrong-text-{target_type}",
|
||||||
|
"label": f"{_type_label(target_type)} nicht im eingereichten Text",
|
||||||
|
"passed": False,
|
||||||
|
"severity": "HIGH",
|
||||||
|
"level": 1,
|
||||||
|
"parent": None,
|
||||||
|
"skipped": False,
|
||||||
|
"matched_text": "",
|
||||||
|
"hint": (
|
||||||
|
f"Der eingereichte Text enthaelt keine "
|
||||||
|
f"{_type_label(target_type)}. Moeglicherweise wurde "
|
||||||
|
f"die falsche URL eingegeben. Das System konnte die "
|
||||||
|
f"{_type_label(target_type)} auch in keinem anderen "
|
||||||
|
f"eingereichten Dokument finden."
|
||||||
|
),
|
||||||
|
"source": "cross_document_search",
|
||||||
|
"doc_type": target_type,
|
||||||
|
})
|
||||||
|
logger.info("Cross-doc: %s text doesn't match doc_type, not found elsewhere", target_type)
|
||||||
|
|
||||||
|
return findings
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_section_by_keywords(
|
||||||
|
text: str, keywords: list[str],
|
||||||
|
) -> str | None:
|
||||||
|
"""Extract the section of text around the keyword matches."""
|
||||||
|
text_lower = text.lower()
|
||||||
|
lines = text.split("\n")
|
||||||
|
|
||||||
|
# Find first and last line containing any keyword
|
||||||
|
first_line = len(lines)
|
||||||
|
last_line = 0
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
line_lower = line.lower()
|
||||||
|
if any(kw in line_lower for kw in keywords):
|
||||||
|
first_line = min(first_line, i)
|
||||||
|
last_line = max(last_line, i)
|
||||||
|
|
||||||
|
if first_line >= last_line:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Expand to include context (5 lines before first, 10 after last)
|
||||||
|
start = max(0, first_line - 5)
|
||||||
|
end = min(len(lines), last_line + 10)
|
||||||
|
|
||||||
|
section = "\n".join(lines[start:end])
|
||||||
|
return section if len(section.split()) >= 30 else None
|
||||||
|
|
||||||
|
|
||||||
|
def _type_label(doc_type: str) -> str:
|
||||||
|
labels = {
|
||||||
|
"widerruf": "Widerrufsbelehrung",
|
||||||
|
"cookie": "Cookie-Richtlinie",
|
||||||
|
"social_media": "Social-Media-Datenschutz",
|
||||||
|
"impressum": "Impressum",
|
||||||
|
"agb": "AGB",
|
||||||
|
"dsb": "DSB-Kontakt",
|
||||||
|
"dse": "Datenschutzerklaerung",
|
||||||
|
}
|
||||||
|
return labels.get(doc_type, doc_type)
|
||||||
|
|||||||
@@ -532,19 +532,43 @@ async def _find_dsi_links(page: Page, base_domain: str) -> list[dict]:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
async def _expand_all_interactive(page: Page) -> None:
|
async def _expand_all_interactive(page: Page) -> None:
|
||||||
"""Expand all accordions, tabs, details, dropdowns on the page."""
|
"""Expand all accordions, tabs, details, dropdowns on the page.
|
||||||
|
|
||||||
|
IMPORTANT: Only expand CLOSED elements. Never click elements that
|
||||||
|
are already expanded (aria-expanded="true") — that would close them.
|
||||||
|
BMW, for example, has accordions open by default.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
await page.evaluate("""() => {
|
await page.evaluate("""() => {
|
||||||
|
// 1. Open all <details> that are closed
|
||||||
document.querySelectorAll('details:not([open])').forEach(d => d.open = true);
|
document.querySelectorAll('details:not([open])').forEach(d => d.open = true);
|
||||||
const sels = ['button[aria-expanded="false"]','[data-toggle="collapse"]',
|
|
||||||
'[data-bs-toggle="collapse"]','[class*="accordion"] > button',
|
// 2. Click buttons that are explicitly CLOSED (aria-expanded="false")
|
||||||
'[class*="collapse"] > button','.panel-heading a'];
|
document.querySelectorAll('button[aria-expanded="false"]').forEach(b => {
|
||||||
sels.forEach(s => document.querySelectorAll(s).forEach(e => { try{e.click()}catch{} }));
|
try { b.click(); } catch {}
|
||||||
document.querySelectorAll('button,a').forEach(b => {
|
});
|
||||||
if (/^(mehr|more|weiterlesen|read more|show more|anzeigen|alle anzeigen)/i.test((b.textContent||'').trim()))
|
|
||||||
try{b.click()}catch{}
|
// 3. Bootstrap/jQuery collapse triggers (only closed ones)
|
||||||
|
document.querySelectorAll('[data-toggle="collapse"].collapsed').forEach(e => {
|
||||||
|
try { e.click(); } catch {}
|
||||||
|
});
|
||||||
|
document.querySelectorAll('[data-bs-toggle="collapse"].collapsed').forEach(e => {
|
||||||
|
try { e.click(); } catch {}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. "Show more" / "Mehr anzeigen" buttons
|
||||||
|
document.querySelectorAll('button,a').forEach(b => {
|
||||||
|
const t = (b.textContent || '').trim();
|
||||||
|
if (/^(mehr|more|weiterlesen|read more|show more|anzeigen|alle anzeigen)/i.test(t))
|
||||||
|
try { b.click(); } catch {}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 5. Tabs — click each to make content visible, then go back
|
||||||
|
// (don't click, just make tab panels visible)
|
||||||
|
document.querySelectorAll('[role="tabpanel"][hidden]').forEach(p => {
|
||||||
|
p.removeAttribute('hidden');
|
||||||
|
p.style.display = '';
|
||||||
});
|
});
|
||||||
document.querySelectorAll('[role="tab"]').forEach(t => { try{t.click()}catch{} });
|
|
||||||
}""")
|
}""")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
**URL:** https://www.bmw.de
|
**URL:** https://www.bmw.de
|
||||||
**Typ:** Konzern / B2C Automobil
|
**Typ:** Konzern / B2C Automobil
|
||||||
**Datum:** 2026-05-12
|
**Datum:** 2026-05-15 (URLs + Inhalte verifiziert)
|
||||||
**Batch-Test:** 8/9 L1, 10/21 L2 (Mangelhaft, 48%)
|
**Vorheriger Batch-Test:** 8/9 L1, 10/21 L2 (Mangelhaft, 48%) — VERALTET, URLs waren alle 404
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -20,83 +20,88 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Dokumente
|
## Dokumente (URLs verifiziert 2026-05-15)
|
||||||
|
|
||||||
| Dokumenttyp | Vorhanden | URL |
|
**ACHTUNG: BMW verteilt Rechtstexte ueber 3 Domains!**
|
||||||
|-------------|-----------|-----|
|
|
||||||
| DSI | Ja | https://www.bmw.de/de/footer/metanavigation/datenschutz.html |
|
| Dokumenttyp | Domain | URL |
|
||||||
| Impressum | Ja | https://www.bmw.de/de/footer/metanavigation/impressum.html |
|
|-------------|--------|-----|
|
||||||
| Cookie-Richtlinie | Ja (separate Seite) | https://www.bmw.de/de/footer/metanavigation/cookie-policy.html |
|
| DSI | bmw.de | https://www.bmw.de/de/footer/metanavigation/data-privacy.html |
|
||||||
| AGB | Ja | TODO: URL verifizieren |
|
| Impressum | bmw.de | https://www.bmw.de/de/footer/metanavigation/legal-notice-pool/imprint.html |
|
||||||
| Widerruf | Ggf. in AGB | — |
|
| Cookie-Richtlinie | bmw.de | https://www.bmw.de/de/footer/footer-section/cookie-policy.html |
|
||||||
| Social Media DSE | Nein | — |
|
| Legal Disclaimer / NB | bmw.de | https://www.bmw.de/de/footer/metanavigation/legal-disclaimer-pool/legal-disclaimer.html |
|
||||||
| Nutzungsbedingungen | Ja | TODO: URL verifizieren |
|
| Konzern-Datenschutz + Widerruf | **bmwgroup.com** | https://www.bmwgroup.com/de/general/data_privacy.html |
|
||||||
| DSB-Kontakt | In DSI | — |
|
| Social Media Privacy | **bmwgroup.jobs** | https://www.bmwgroup.jobs/de/de/services/social-media-privacy-policy.html |
|
||||||
|
| DSB-Kontakt | bmw.de (in DSI) | datenschutz@bmw.de |
|
||||||
|
| AGB | **Nicht gefunden** | Kein oeffentlich verlinktes AGB-Dokument |
|
||||||
|
|
||||||
|
**Finding: Rechtstexte ueber 3 Domains verteilt (bmw.de, bmwgroup.com, bmwgroup.jobs). Fuer Betroffene schwer auffindbar. Social Media Policy nur ueber Karriere-Portal erreichbar.**
|
||||||
|
|
||||||
|
**DSI hat Sub-Pages:** data-category.html, privacy-subpage-weblink-a/c/d/e.html
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verifizierte Inhalte: Impressum
|
||||||
|
|
||||||
|
| Feld | Wert |
|
||||||
|
|------|------|
|
||||||
|
| Firma | Bayerische Motoren Werke Aktiengesellschaft (BMW AG) |
|
||||||
|
| Anschrift | Petuelring 130, 80809 Muenchen |
|
||||||
|
| Vorstand | Milan Nedeljkovic (Vorsitzender), 6 weitere |
|
||||||
|
| Aufsichtsrat | Nicolas Peter (Vorsitzender) |
|
||||||
|
| Registergericht | AG Muenchen, HRB 42243 |
|
||||||
|
| USt-IdNr | DE129273398 |
|
||||||
|
| Kontakt | kundenbetreuung@bmw.de |
|
||||||
|
| Versicherungsvermittler | §34d Abs. 6 GewO, IHK Muenchen |
|
||||||
|
|
||||||
|
## Verifizierte Inhalte: DSB
|
||||||
|
|
||||||
|
| Feld | Wert |
|
||||||
|
|------|------|
|
||||||
|
| Titel | Data Protection Officer BMW AG |
|
||||||
|
| Anschrift | Petuelring 130, 80788 Muenchen |
|
||||||
|
| E-Mail | datenschutz@bmw.de |
|
||||||
|
|
||||||
|
## Verifizierte Inhalte: Social Media
|
||||||
|
|
||||||
|
Impressum gilt auch fuer:
|
||||||
|
- Facebook: facebook.com/BMWDeutschland
|
||||||
|
- Instagram: instagram.com/bmwdeutschland
|
||||||
|
- YouTube: youtube.com/user/BMWDeutschland
|
||||||
|
- Pinterest: de.pinterest.com/bmwdeutschland
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Erwartete Ergebnisse: DSI (Art. 13 DSGVO)
|
## Erwartete Ergebnisse: DSI (Art. 13 DSGVO)
|
||||||
|
|
||||||
### L1 Checks (8/9)
|
### L1 Checks
|
||||||
|
|
||||||
| Check | Erwartet | Begruendung |
|
| Check | Erwartet | Begruendung |
|
||||||
|-------|----------|-------------|
|
|-------|----------|-------------|
|
||||||
| Verantwortlicher | PASS | BMW AG, Muenchen |
|
| Verantwortlicher | PASS | BMW AG, Petuelring 130, 80809 Muenchen |
|
||||||
| DSB | PASS | DSB erwaehnt |
|
| DSB | PASS | datenschutz@bmw.de, Petuelring 130 |
|
||||||
| Zwecke | PASS | Ausfuehrlich |
|
| Zwecke | PASS | Ausfuehrlich (Sub-Pages) |
|
||||||
| Rechtsgrundlage | PASS | Art. 6 Referenzen |
|
| Rechtsgrundlage | PASS | Art. 6 Referenzen |
|
||||||
| Empfaenger | PASS | Kategorien aufgezaehlt |
|
| Empfaenger | PASS | Kategorien aufgezaehlt |
|
||||||
| Drittlandtransfer | PASS | USA-Transfer erwaehnt |
|
| Drittlandtransfer | PASS | USA-Transfer erwaehnt |
|
||||||
| Speicherdauer | PASS | Zeitangaben vorhanden |
|
| Speicherdauer | PASS | Zeitangaben vorhanden |
|
||||||
| Betroffenenrechte | **FAIL** | Rechte ohne Art.-Referenzen |
|
| Betroffenenrechte | Zu pruefen | Art. 15-21 in DSI? |
|
||||||
| Beschwerderecht | **FAIL** | Art. 77 nicht explizit erwaehnt |
|
| Beschwerderecht | Zu pruefen | Art. 77 in DSI? |
|
||||||
|
|
||||||
### L2 Checks (10/21 — verifizierte True Positives)
|
|
||||||
|
|
||||||
| Check | Erwartet | TP/FP |
|
|
||||||
|-------|----------|-------|
|
|
||||||
| Anschrift | PASS | — |
|
|
||||||
| E-Mail | **FAIL** | **TP** — Keine direkte E-Mail-Adresse fuer DSB angegeben |
|
|
||||||
| Telefon | PASS | — |
|
|
||||||
| DSB Kontakt | PASS | — |
|
|
||||||
| Art. 6(1)(a) | PASS | — |
|
|
||||||
| Art. 6(1)(b) | PASS | — |
|
|
||||||
| Art. 6(1)(f) | PASS | — |
|
|
||||||
| Interessenabwaegung | **FAIL** | **TP** — Keine dokumentierte Abwaegung |
|
|
||||||
| Transfermechanismus | **FAIL** | **TP** — Kein SCC/DPF benannt |
|
|
||||||
| Art. 15-18,20,21 | **FAIL** | **TP** — Rechte ohne Artikel-Referenzen aufgezaehlt |
|
|
||||||
| Art. 22 Profiling | **FAIL** | **TP** — Kein Profiling-Hinweis trotz Konfigurator/Personalisierung |
|
|
||||||
| Aufsichtsbehoerde | **FAIL** | **TP** — Keine konkrete Behoerde benannt |
|
|
||||||
| Loeschkonzept | **FAIL** | **TP** — Kein Loeschkonzept referenziert |
|
|
||||||
|
|
||||||
**Verifiziert: BMW hat tatsaechlich eine lueckenhafte DSI. Die Findings sind True Positives.**
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Erwartete Ergebnisse: Impressum
|
## Erwartete Ergebnisse: Impressum
|
||||||
|
|
||||||
| Check | Erwartet | Begruendung |
|
|
||||||
|-------|----------|-------------|
|
|
||||||
| Firmenname | PASS | BMW AG |
|
|
||||||
| Anschrift | PASS | Petuelring 130, 80809 Muenchen |
|
|
||||||
| Vertretung | PASS | Vorstand benannt |
|
|
||||||
| Registergericht | PASS | AG Muenchen, HRB 42243 |
|
|
||||||
| USt-IdNr | PASS | DE 129 273 987 |
|
|
||||||
| V.i.S.d.P. | PASS | Hat redaktionelle Inhalte |
|
|
||||||
| Streitbeilegung | AKTIV | B2C mit Online-Angebot → ODR relevant |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Erwartete Ergebnisse: Cookie-Richtlinie
|
|
||||||
|
|
||||||
| Check | Erwartet |
|
| Check | Erwartet |
|
||||||
|-------|----------|
|
|-------|----------|
|
||||||
| Cookie-Arten | PASS (Essential, Analytics, Marketing) |
|
| Firmenname | PASS (BMW AG) |
|
||||||
| Cookie-Zwecke | PASS |
|
| Anschrift | PASS (Petuelring 130, 80809 Muenchen) |
|
||||||
| Speicherdauern | TODO: verifizieren |
|
| Vertretung | PASS (Vorstand benannt) |
|
||||||
| Drittanbieter | PASS (Google, Meta etc.) |
|
| USt-IdNr | PASS (DE129273398) |
|
||||||
| Rechtsgrundlage | TODO: §25 TDDDG? |
|
| Registergericht | PASS (HRB 42243) |
|
||||||
| Consent-Tool | PASS (OneTrust o.ae.) |
|
| V.i.S.d.P. | PASS (Medienunternehmen mit Blog) |
|
||||||
|
| Streitbeilegung | AKTIV (B2C) |
|
||||||
|
| Versicherungsvermittler | PASS (§34d GewO, IHK Muenchen) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -106,16 +111,7 @@
|
|||||||
|------|----------|
|
|------|----------|
|
||||||
| banner_detected | true |
|
| banner_detected | true |
|
||||||
| provider | OneTrust oder aehnlich |
|
| provider | OneTrust oder aehnlich |
|
||||||
| violations | Mehrere (grosser Konzern mit viel Tracking) |
|
| violations | Zu pruefen |
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Cross-Check Banner vs Cookie
|
|
||||||
|
|
||||||
| Finding | Erwartet |
|
|
||||||
|---------|----------|
|
|
||||||
| Dienste fehlen in Cookie-RL | Moeglich (viele Third-Party-Tracker) |
|
|
||||||
| Tracking vor Consent | Moeglich (Pre-Consent Analytics) |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -124,6 +120,6 @@
|
|||||||
| Check | Filter | Begruendung |
|
| Check | Filter | Begruendung |
|
||||||
|-------|--------|-------------|
|
|-------|--------|-------------|
|
||||||
| ODR | AKTIV | B2C mit Online-Angebot |
|
| ODR | AKTIV | B2C mit Online-Angebot |
|
||||||
| Widerruf | AKTIV | B2C |
|
| Widerruf | In DSI | Marketing-Consent widerrufbar |
|
||||||
| Berufsrecht | SKIP | Kein regulierter Beruf |
|
| Berufsrecht | SKIP | Kein regulierter Beruf |
|
||||||
| V.i.S.d.P. | AKTIV | Hat Magazine/Blog |
|
| V.i.S.d.P. | AKTIV | Hat Magazine/Blog |
|
||||||
|
|||||||
@@ -2,172 +2,185 @@
|
|||||||
|
|
||||||
**URL:** https://www.spiegel.de
|
**URL:** https://www.spiegel.de
|
||||||
**Typ:** Medien / Nachrichtenportal
|
**Typ:** Medien / Nachrichtenportal
|
||||||
**Datum:** 2026-05-13 (verifiziert gegen Live-Texte)
|
**Datum:** 2026-05-14 (verifiziert gegen Live-Texte + System-Ergebnis)
|
||||||
**Vorheriger Batch-Test:** 6/9 L1, 10/13 L2 — VERALTET, mehrere False Negatives
|
**Volltext:** [06-spiegel-dsi-fulltext.txt](06-spiegel-dsi-fulltext.txt) (13.698 Woerter, 107.720 Zeichen)
|
||||||
**Volltext:** [06-spiegel-dsi-fulltext.txt](06-spiegel-dsi-fulltext.txt) (13.705 Woerter, 107.720 Zeichen)
|
|
||||||
**Root Cause aller FN:** API-Limit `text[:50000]` schnitt bei 47% ab → DSB/Art.77/Rechte fehlten
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Business Profile (erwartet)
|
## Business Profile (erwartet vs tatsaechlich)
|
||||||
|
|
||||||
| Feld | Erwarteter Wert | Begruendung |
|
| Feld | Erwartet | System-Ergebnis | |
|
||||||
|------|----------------|-------------|
|
|------|---------|----------------|---|
|
||||||
| business_type | b2c | Abo-Modell (Spiegel+) |
|
| business_type | b2c | B2C | ✓ |
|
||||||
| industry | media | Nachrichtenportal |
|
| industry | media | media | ✓ |
|
||||||
| has_online_shop | true | Spiegel+ Abo-Shop |
|
| has_online_shop | true | true | ✓ |
|
||||||
| has_editorial_content | true | Kerngeschaeft |
|
| has_editorial_content | true | true | ✓ |
|
||||||
| is_regulated_profession | **false** | Kein regulierter Beruf. "Anwalt" im Text ist Redaktionsanwalt, kein Kanzlei-Beruf |
|
| is_regulated_profession | false | false | ✓ (gefixt, war FP "anwalt") |
|
||||||
| needs_odr | true | B2C mit Online-Abo |
|
| needs_odr | true | true | ✓ |
|
||||||
|
| detected_services | 31 | 10 angezeigt (31 intern) | UI zeigt nur Top 10 |
|
||||||
**Bug:** Profiler erkennt "anwalt" im Impressum-Text und setzt is_regulated_profession=true. FALSE POSITIVE.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Dokumente
|
## Dokumente
|
||||||
|
|
||||||
| Dokumenttyp | Vorhanden | URL | Anmerkung |
|
| Dokumenttyp | Vorhanden | URL | System-Ergebnis |
|
||||||
|-------------|-----------|-----|-----------|
|
|-------------|-----------|-----|----------------|
|
||||||
| DSI | Ja | https://www.spiegel.de/datenschutz-spiegel | 6461 Woerter, 11 Abschnitte, sehr ausfuehrlich |
|
| DSI | Ja | https://www.spiegel.de/datenschutz-spiegel | **9/9 L1 (100%)** ✓ |
|
||||||
| Impressum | Ja | https://www.spiegel.de/impressum | 2 Gesellschaften (DER SPIEGEL GmbH + SPIEGEL-Verlag) |
|
| Impressum | Ja | https://www.spiegel.de/impressum | **9/13 L1 (86%)** ✓ |
|
||||||
| Cookie-Richtlinie | In DSI Abschnitt 4 | #funktionsfaehigkeitdesangebots | Sourcepoint CMP |
|
| Social Media | In DSI (Abschnitt 8) | auto-filled | **10/10 L1 (100%)** ✓ |
|
||||||
| AGB | Ja | https://www.spiegel.de/agb | Abo-Bedingungen |
|
| Cookie-RL | In DSI (Abschnitt 4) | auto-filled | 1/6 L1 (17%) |
|
||||||
| Nutzungsbedingungen | Ja | https://www.spiegel.de/nutzungsbedingungen | Separates Dokument |
|
| AGB | Ja | https://www.spiegel.de/agb | Nicht eingegeben |
|
||||||
| Widerruf | In AGB Abschnitt 10 | https://www.spiegel.de/agb | "Widerrufsrecht fuer Abonnements" |
|
| Nutzungsbedingungen | Ja | https://www.spiegel.de/nutzungsbedingungen | 5/12 L1 (42%) |
|
||||||
| Social Media DSE | In DSI Abschnitt 8 | #einbinden-von-drittinhalten | Facebook, YouTube, X, Instagram, TikTok, etc. |
|
| Widerruf | In AGB §10 | Falsch zugewiesen (NB-Text) | 0/8 L1 (0%) |
|
||||||
| DSB-Kontakt | In DSI | — | dsb@spiegelgruppe.de |
|
| DSB-Kontakt | In DSI | auto-filled | **9/9 L1 (100%)** ✓ |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Erwartete Ergebnisse: DSI (Art. 13 DSGVO)
|
## DSI (Art. 13 DSGVO) — 9/9 L1, 24/42 L2
|
||||||
|
|
||||||
### L1 Checks (ERWARTET: 9/9 PASS)
|
### L1 Checks (9/9 PASS)
|
||||||
|
|
||||||
| Check | Erwartet | Beleg | Unser Ergebnis | Bug? |
|
| Check | Erwartet | System | Beleg |
|
||||||
|-------|----------|-------|----------------|------|
|
|-------|----------|--------|-------|
|
||||||
| Verantwortlicher | PASS | "DER SPIEGEL GmbH & Co. KG, Ericusspitze 1, 20459 Hamburg" | PASS (3/3) | — |
|
| Verantwortlicher | PASS | PASS (3/3) | Ericusspitze 1, 20459 Hamburg |
|
||||||
| DSB | **PASS** | "z. Hd. der Datenschutzbeauftragten... dsb@spiegelgruppe.de" | **FAIL** | **FN — Regex matcht "Datenschutzbeauftragte" nicht ohne "r" am Ende oder erkennt Kontext nicht** |
|
| DSB | PASS | PASS (1/1) | "z. Hd. der Datenschutzbeauftragten... dsb@spiegelgruppe.de" |
|
||||||
| Zwecke | PASS | Adobe-Tracking, Vertragsbeziehungen, Drittinhalte etc. | PASS | — |
|
| Zwecke | PASS | PASS (1/1) | Adobe-Tracking, Vertragsbeziehungen etc. |
|
||||||
| Rechtsgrundlage | PASS | Art. 6(1)(a), (b), (f) explizit | PASS (3/4) | — |
|
| Rechtsgrundlage | PASS | PASS (3/4) | Art. 6(1)(a), (b), (f) |
|
||||||
| Empfaenger | PASS | Server-/Applikationsbetreiber, Auftragsverarbeiter | PASS (2/2) | — |
|
| Empfaenger | PASS | PASS (2/2) | AVV erwaehnt |
|
||||||
| Drittlandtransfer | PASS | SCC erwaehnt | PASS (1/1) | — |
|
| Drittlandtransfer | PASS | PASS (1/1) | SCC erwaehnt |
|
||||||
| Speicherdauer | PASS | "30 Tage" Protokolldatei | PASS (1/2) | — |
|
| Speicherdauer | PASS | PASS (2/2) | "30 Tage", Loeschfristen |
|
||||||
| Betroffenenrechte | **PASS** | Art. 15, 16, 17, 18, 21 explizit. Art. 20 fehlt. | **FAIL** | **FN — Regex verlangt alle 6 Artikel, 5/6 genuegen nicht** |
|
| Betroffenenrechte | PASS | PASS (6/7) | Art. 15-18, 20, 21. Art. 22 fehlt (TP) |
|
||||||
| Beschwerderecht | **PASS** | "Art. 77 DSGVO... HmbBfDI... Ludwig-Ehrhard-Str. 22" | **FAIL** | **FN — Regex findet Art. 77 + HmbBfDI nicht** |
|
| Beschwerderecht | PASS | PASS (1/1) | HmbBfDI, Art. 77 |
|
||||||
|
|
||||||
**3 False Negatives in L1!** DSB, Betroffenenrechte, Beschwerderecht sind alle vorhanden.
|
### L2 True Positives (korrekte Findings)
|
||||||
|
|
||||||
### L2 Checks (Stichproben)
|
| Check | Status | Begruendung |
|
||||||
|
|
||||||
| Check | Erwartet | Beleg | Unser Ergebnis | Bug? |
|
|
||||||
|-------|----------|-------|----------------|------|
|
|
||||||
| E-Mail | PASS | datenschutz@spiegelgruppe.de | PASS | — |
|
|
||||||
| Interessenabwaegung | FAIL (TP) | Interesse benannt, keine Abwaegung | FAIL | Korrekt |
|
|
||||||
| Art. 20 Portabilitaet | FAIL (TP) | Art. 20 fehlt im Rechte-Abschnitt | — | Korrekter Finding |
|
|
||||||
| Loeschkonzept | FAIL (TP) | Kein formales Loeschkonzept | FAIL | Korrekt |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Erwartete Ergebnisse: Impressum (§5 TMG)
|
|
||||||
|
|
||||||
| Check | Erwartet | Beleg | Unser Ergebnis | Bug? |
|
|
||||||
|-------|----------|-------|----------------|------|
|
|
||||||
| Firmenname | PASS | DER SPIEGEL GmbH & Co. KG + SPIEGEL-Verlag | PASS | — |
|
|
||||||
| Anschrift | PASS | Ericusspitze 1, 20457 Hamburg | PASS | — |
|
|
||||||
| Kontakt | PASS | Tel. 040 3007-0, spiegel@spiegel.de | PASS | — |
|
|
||||||
| Register | PASS | HRA 123 261 + HRA 61 755 | PASS | — |
|
|
||||||
| USt-IdNr | **PASS** | DE 212 442 423 + DE 118 922 410 | **FAIL** | **FN — Regex findet "Umsatzsteuer-ID:" Format nicht** |
|
|
||||||
| Vertretung | PASS | Thomas Hass (Geschaeftsfuehrung) | PASS (1/1) | — |
|
|
||||||
| V.i.S.d.P. | **PASS** | "Verantwortlicher i. S. v. § 18 Abs. 2 MStV: Dirk Kurbjuweit" | **FAIL** | **FN — Regex sucht "v.i.s.d.p." nicht "verantwortlicher i.s.v."** |
|
|
||||||
| Streitbeilegung | PASS | ODR-Link vorhanden (in AGB) | PASS | — |
|
|
||||||
| Berufsrecht | **SKIP** | Spiegel ist kein regulierter Beruf | **AKTIV (1/3)** | **FP — Profiler "anwalt" Bug** |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Erwartete Ergebnisse: AGB
|
|
||||||
|
|
||||||
| Check | Erwartet | Beleg |
|
|
||||||
|-------|----------|-------|
|
|
||||||
| Geltungsbereich | PASS | Abschnitt 1 |
|
|
||||||
| Vertragsschluss | PASS | Abschnitt 2 |
|
|
||||||
| Preise/Zahlung | PASS | Abschnitte 4-7 |
|
|
||||||
| Kuendigung | PASS | Abschnitt 8 (1 Monat Frist) |
|
|
||||||
| Widerrufsrecht | PASS | Abschnitt 10 (14 Tage, Muster-Formular) |
|
|
||||||
| §312k Button | Zu pruefen | Kuendigungsbutton Pflicht seit 01.07.2022 |
|
|
||||||
| ODR-Link | PASS | http://ec.europa.eu/consumers/odr/ |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Erwartete Ergebnisse: Widerrufsbelehrung (AGB §10)
|
|
||||||
|
|
||||||
| Check | Erwartet | Beleg |
|
|
||||||
|-------|----------|-------|
|
|
||||||
| Belehrung | PASS | "Sie haben das Recht, Abonnementvertraege binnen 14 Tagen ohne Angabe von Gruenden zu widerrufen" |
|
|
||||||
| 14-Tage-Frist | PASS | Explizit genannt |
|
|
||||||
| Form | PASS | Brief, E-Mail, Fax |
|
|
||||||
| Muster-Formular | PASS | "beigefuegte Muster-Widerrufsformular" erwaehnt |
|
|
||||||
| Folgen | PASS | Rueckerstattungsregeln beschrieben |
|
|
||||||
| Empfaenger | PASS | DER SPIEGEL Abonnentenservice, 20637 Hamburg; aboservice@spiegel.de |
|
|
||||||
| Ausnahme digitale Inhalte | PASS | "Fuer sofort nutzbare Zeitzugaenge... kein Widerrufsrecht" |
|
|
||||||
|
|
||||||
**Problem:** Unser Check prueft den DSI-Volltext gegen Widerruf-Checklist statt die AGB. Der Widerruf steht in den AGB (§10), nicht in der DSI.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Erwartete Ergebnisse: Social Media (DSI Abschnitt 8)
|
|
||||||
|
|
||||||
| Check | Erwartet | Beleg |
|
|
||||||
|-------|----------|-------|
|
|
||||||
| Gemeinsam Verantwortliche | PASS | Erwaehnt |
|
|
||||||
| Meta konkret benannt | FAIL (TP) | Nur "Facebook" ohne "Meta Platforms Ireland Ltd." |
|
|
||||||
| Vereinbarung Art. 26 | FAIL (TP) | Kein Page Controller Addendum |
|
|
||||||
| Plattformen | PASS | Facebook, YouTube, X, Instagram, TikTok, Vimeo, Reddit, Bluesky, etc. |
|
|
||||||
| SCC | PASS | Erwaehnt |
|
|
||||||
| DPF | FAIL (TP) | Data Privacy Framework nicht erwaehnt |
|
|
||||||
| Rechtsgrundlage | PASS | Art. 6(1)(f) |
|
|
||||||
| Alle standardmaessig deaktiviert | PASS | "standardmaessig deaktiviert" |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Banner-Check
|
|
||||||
|
|
||||||
| Feld | Erwartet |
|
|
||||||
|------|----------|
|
|
||||||
| banner_detected | true |
|
|
||||||
| provider | Sourcepoint |
|
|
||||||
| tcf_enabled | true |
|
|
||||||
| Vendor-Anzahl | 40+ (grosses Medienunternehmen) |
|
|
||||||
| violations | Consent-Wall blockiert Zugang → moeglicherweise unzulaessig |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Cross-Check Banner vs DSI
|
|
||||||
|
|
||||||
| Finding | Erwartet |
|
|
||||||
|---------|----------|
|
|
||||||
| Vendors fehlen in DSI | Wahrscheinlich — viele TCF-Vendors nicht in DSI dokumentiert |
|
|
||||||
| Tracking vor Consent | Unwahrscheinlich (Sourcepoint blockiert gut) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Kontext-Filter
|
|
||||||
|
|
||||||
| Check | Filter | Begruendung |
|
|
||||||
|-------|--------|-------------|
|
|-------|--------|-------------|
|
||||||
| ODR | AKTIV | B2C Online-Abo |
|
| Interessenabwaegung | FAIL (TP) | Interesse benannt, keine Abwaegung dokumentiert |
|
||||||
| Widerruf | AKTIV | B2C |
|
| Art. 22 Profiling | FAIL (TP) | Nicht erwaehnt trotz personalisierter Werbung |
|
||||||
| V.i.S.d.P. | AKTIV | Medienunternehmen (Kernpflicht) |
|
|
||||||
| Berufsrecht | **SKIP** | Kein regulierter Beruf |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Identifizierte Regex-Bugs (aus diesem GT-Abgleich)
|
## Impressum — 9/13 L1, 9/31 L2
|
||||||
|
|
||||||
| # | Check | Bug | Beleg auf Website | Regex-Problem |
|
| Check | Erwartet | System | |
|
||||||
|---|-------|-----|-------------------|---------------|
|
|-------|----------|--------|---|
|
||||||
| 1 | DSB | FN | "z. Hd. der Datenschutzbeauftragten... dsb@spiegelgruppe.de" | Regex matcht "Datenschutzbeauftragten" (Genitiv/Dativ) nicht |
|
| Firmenname | PASS | PASS | ✓ |
|
||||||
| 2 | Beschwerderecht | FN | "Art. 77 DSGVO... HmbBfDI" | Regex findet "Art. 77" oder "Aufsichtsbehoerde" nicht im Spiegel-Text |
|
| Anschrift | PASS | PASS (2/2) | ✓ |
|
||||||
| 3 | Betroffenenrechte | FN | Art. 15, 16, 17, 18, 21 — nur Art. 20 fehlt | Regex verlangt ALLE 6, 5/6 ist nicht genug |
|
| Kontakt | PASS | PASS (2/2) | ✓ |
|
||||||
| 4 | V.i.S.d.P. | FN | "Verantwortlicher i. S. v. § 18 Abs. 2 MStV" | Regex sucht nur "v.i.s.d.p.", nicht die MStV-Formulierung |
|
| Register | PASS | PASS (2/2) | ✓ |
|
||||||
| 5 | USt-IdNr | FN | "Umsatzsteuer-ID: DE 212 442 423" | Regex sucht "ust-idnr" oder "ust-id", matcht "umsatzsteuer-id:" nicht |
|
| USt-IdNr | PASS | PASS (1/1) | ✓ Gefixt ("Umsatzsteuer-ID:" + DE mit Leerzeichen) |
|
||||||
| 6 | Profiler "anwalt" | FP | Redaktionsanwalt im Impressum | "anwalt" zu generisch, matcht Personennamen/Rollen |
|
| Vertretung | PASS | PASS (1/1) | ✓ |
|
||||||
|
| V.i.S.d.P. | PASS | PASS | ✓ Gefixt ("Verantwortlicher i.S.v. §18 MStV") |
|
||||||
|
| Streitbeilegung | PASS | PASS | ✓ |
|
||||||
|
| Berufsrecht | SKIP | PASS (1/3) | FP — "Berufsrechtliche Regelungen" matcht falsch |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Social Media — 10/10 L1, 12/30 L2
|
||||||
|
|
||||||
|
| Check | Erwartet | System | |
|
||||||
|
|-------|----------|--------|---|
|
||||||
|
| Gemeinsam Verantwortliche | PASS | PASS | ✓ |
|
||||||
|
| Meta benannt | PASS | PASS | ✓ "Meta Platforms Inc" erkannt |
|
||||||
|
| Vereinbarung Art. 26 | PASS | PASS (1/2) | ✓ Seiteninsights erwaehnt |
|
||||||
|
| Anlaufstelle | PASS | PASS (1/1) | ✓ |
|
||||||
|
| Plattformen | PASS | PASS (1/1) | ✓ |
|
||||||
|
| Drittlandtransfer | PASS | PASS (2/2) | ✓ SCC + DPF |
|
||||||
|
| Rechtsgrundlage | PASS | PASS (1/1) | ✓ |
|
||||||
|
| Betroffenenrechte | PASS | PASS (1/1) | ✓ Opt-Out erwaehnt |
|
||||||
|
| Social Bookmarks | PASS | PASS | ✓ |
|
||||||
|
|
||||||
|
### L2 True Positives
|
||||||
|
|
||||||
|
| Check | Status | Begruendung |
|
||||||
|
|-------|--------|-------------|
|
||||||
|
| Page Controller Addendum | FAIL (TP) | Nicht verlinkt |
|
||||||
|
| 2-Klick-Loesung | FAIL (TP) | Nicht dokumentiert |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cookie-Richtlinie — 1/6 L1
|
||||||
|
|
||||||
|
Cookie-Infos stehen bei Spiegel im **Sourcepoint-Banner** und in DSI Abschnitt 4, nicht als eigenes Dokument. Section-Splitter hat einen kurzen Cookie-Abschnitt extrahiert, aber die meisten Checks scheitern weil die Details im Banner stehen (nicht im Text).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Nutzungsbedingungen — 5/12 L1
|
||||||
|
|
||||||
|
Aus spiegel.de/nutzungsbedingungen extrahiert (1679 Woerter). Echte Luecken bei Einbeziehungsklausel, ODR-Link, Kuendigung, Zahlungsarten.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Widerrufsbelehrung — 0/8 L1
|
||||||
|
|
||||||
|
**Problem:** System prueft Nutzungsbedingungen-Text (1679w) statt AGB-Text.
|
||||||
|
**Tatsaechlich:** Widerrufsbelehrung steht in AGB §10 (spiegel.de/agb):
|
||||||
|
- 14-Tage-Frist ✓
|
||||||
|
- Muster-Widerrufsformular ✓
|
||||||
|
- Empfaenger (DER SPIEGEL Abonnentenservice) ✓
|
||||||
|
- Ausnahme digitale Inhalte ✓
|
||||||
|
|
||||||
|
**Offener Punkt:** Cross-Document Intelligence — System muss erkennen dass der Text keine Widerrufsbelehrung ist und den AGB-Link vorschlagen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Erkannte Dienste (31/32 = 97%)
|
||||||
|
|
||||||
|
| Dienst | Kategorie | Land | EU | In DSI erwaehnt |
|
||||||
|
|--------|----------|------|----|----------------|
|
||||||
|
| Adobe | tracking | US | Nein | Ja |
|
||||||
|
| Bluesky | social | US | Nein | Ja |
|
||||||
|
| Facebook | social | US | Nein | Ja |
|
||||||
|
| Giphy | content | US | Nein | Ja |
|
||||||
|
| Google Ads | marketing | US | Nein | Ja |
|
||||||
|
| Google reCAPTCHA | security | US | Nein | Ja |
|
||||||
|
| ID5 | identity | GB | Ja | Ja |
|
||||||
|
| IQD | marketing | DE | Ja | Ja |
|
||||||
|
| Imgur | content | US | Nein | Ja |
|
||||||
|
| Instagram | social | US | Nein | Ja |
|
||||||
|
| JW Player | video | US | Nein | Ja |
|
||||||
|
| LinkedIn | marketing | US | Nein | Ja |
|
||||||
|
| Mapbox | maps | US | Nein | Ja |
|
||||||
|
| Meta Platforms | social | US | Nein | Ja |
|
||||||
|
| Microsoft | cloud | US | Nein | Ja |
|
||||||
|
| Omnystudio | audio | CA | Nein | Ja |
|
||||||
|
| PayPal | payment | US | Nein | Ja |
|
||||||
|
| Qualtrics | survey | US | Nein | Ja |
|
||||||
|
| Reddit | social | US | Nein | Ja |
|
||||||
|
| Salesforce | crm | US | Nein | Ja |
|
||||||
|
| Segment | tag_manager | US | Nein | Ja |
|
||||||
|
| Sourcepoint | cmp | US | Nein | Ja |
|
||||||
|
| Spotify | audio | SE | Ja | Ja |
|
||||||
|
| Storifyme | content | DE | Ja | Ja |
|
||||||
|
| TikTok | social | IE | Ja | Ja |
|
||||||
|
| Utiq | tracking | BE | Ja | Ja |
|
||||||
|
| Vimeo | video | US | Nein | Ja |
|
||||||
|
| X/Twitter | social | US | Nein | Ja |
|
||||||
|
| YouTube | video | US | Nein | Ja |
|
||||||
|
| Zendesk | chatbot | US | Nein | Ja |
|
||||||
|
|
||||||
|
**25 Non-EU Dienste, 6 EU-Dienste.** Alle in DSI erwaehnt (Spiegel dokumentiert seine Dienste gut).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fixes die in dieser Session angewendet wurden
|
||||||
|
|
||||||
|
| # | Bug | Fix | Auswirkung |
|
||||||
|
|---|-----|-----|-----------|
|
||||||
|
| 1 | Text-Limit 50k Zeichen | → 200k | DSI: 6461→13698 Woerter, 5 FN weg |
|
||||||
|
| 2 | USt-IdNr "Umsatzsteuer-ID:" | Regex erweitert | Impressum: +1 PASS |
|
||||||
|
| 3 | V.i.S.d.P. "i.S.v. §18 MStV" | Regex + Pattern | Impressum: +1 PASS |
|
||||||
|
| 4 | "anwalt" FP im Profiler | Nur Impressum[:500], nur "rechtsanwalt" | Profiler: FP weg |
|
||||||
|
| 5 | Service-Erkennung 20→118 | service_detector.py | 5→31 Dienste erkannt |
|
||||||
|
| 6 | Section-Splitter auto-fill | auto_fill_from_dsi() | Cookie+Social Media auto-gefuellt |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Offene Punkte
|
||||||
|
|
||||||
|
1. **Widerruf falsch zugewiesen** — System braucht Cross-Document Intelligence (AGB-Link finden)
|
||||||
|
2. **Cookie-RL 1/6** — Cookie-Infos stehen im Banner, nicht im Text → TCF-Vendor-Extraktion wuerde helfen
|
||||||
|
3. **Dienste UI zeigt nur 10** — 31 erkannt aber Frontend kuerzt
|
||||||
|
4. **Berufsrecht FP** — "Berufsrechtliche Regelungen + Zugang" matcht falsch im Spiegel-Impressum
|
||||||
|
5. **Banner-Check nicht sichtbar** — Sourcepoint-Buttons nicht klickbar im Scanner
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Batch Ground Truth Test — run compliance check on all 10 GT websites.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 batch_gt_test.py [--backend-url URL]
|
||||||
|
|
||||||
|
Calls the compliance-check API for each website's DSI + Impressum URLs,
|
||||||
|
polls for results, and prints a comparison table.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
# 10 GT websites with their known document URLs
|
||||||
|
GT_WEBSITES = [
|
||||||
|
{
|
||||||
|
"name": "SafetyKon",
|
||||||
|
"documents": [
|
||||||
|
{"doc_type": "dse", "url": "https://safetykon.de/datenschutz"},
|
||||||
|
{"doc_type": "impressum", "url": "https://safetykon.de/impressum"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "IHK Konstanz",
|
||||||
|
"documents": [
|
||||||
|
{"doc_type": "dse", "url": "https://www.ihk.de/konstanz/servicemarken/ueber-uns/downloads/datenschutzinformationen-zum-internetangebot-4163288"},
|
||||||
|
{"doc_type": "impressum", "url": "https://www.ihk.de/konstanz/impressum"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Stadt Koeln",
|
||||||
|
"documents": [
|
||||||
|
{"doc_type": "dse", "url": "https://www.stadt-koeln.de/datenschutz"},
|
||||||
|
{"doc_type": "impressum", "url": "https://www.stadt-koeln.de/impressum"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BMW",
|
||||||
|
"documents": [
|
||||||
|
{"doc_type": "dse", "url": "https://www.bmw.de/de/footer/metanavigation/data-privacy.html"},
|
||||||
|
{"doc_type": "impressum", "url": "https://www.bmw.de/de/footer/metanavigation/legal-notice-pool/imprint.html"},
|
||||||
|
{"doc_type": "cookie", "url": "https://www.bmw.de/de/footer/footer-section/cookie-policy.html"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sparkasse Bodensee",
|
||||||
|
"documents": [
|
||||||
|
{"doc_type": "dse", "url": "https://www.sparkasse-bodensee.de/de/home/toolbar/datenschutz.html"},
|
||||||
|
{"doc_type": "impressum", "url": "https://www.sparkasse-bodensee.de/de/home/toolbar/impressum.html"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Spiegel",
|
||||||
|
"documents": [
|
||||||
|
{"doc_type": "dse", "url": "https://www.spiegel.de/datenschutz-spiegel"},
|
||||||
|
{"doc_type": "impressum", "url": "https://www.spiegel.de/impressum"},
|
||||||
|
{"doc_type": "nutzungsbedingungen", "url": "https://www.spiegel.de/nutzungsbedingungen"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TUEV Sued",
|
||||||
|
"documents": [
|
||||||
|
{"doc_type": "dse", "url": "https://www.tuvsud.com/de-de/datenschutzerklaerung"},
|
||||||
|
{"doc_type": "impressum", "url": "https://www.tuvsud.com/de-de/impressum"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ETO Gruppe",
|
||||||
|
"documents": [
|
||||||
|
{"doc_type": "dse", "url": "https://www.etogruppe.com/datenschutz.html"},
|
||||||
|
{"doc_type": "impressum", "url": "https://www.etogruppe.com/impressum.html"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Caritas",
|
||||||
|
"documents": [
|
||||||
|
{"doc_type": "dse", "url": "https://www.caritas.de/datenschutz"},
|
||||||
|
{"doc_type": "impressum", "url": "https://www.caritas.de/impressum"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BfDI",
|
||||||
|
"documents": [
|
||||||
|
{"doc_type": "dse", "url": "https://www.bfdi.bund.de/DE/Meta/Datenschutz/datenschutz_node.html"},
|
||||||
|
{"doc_type": "impressum", "url": "https://www.bfdi.bund.de/DE/Meta/Impressum/impressum_node.html"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def run_check(backend_url: str, website: dict) -> dict:
|
||||||
|
"""Submit compliance check and poll for results."""
|
||||||
|
with httpx.Client(timeout=30.0, verify=False) as client:
|
||||||
|
# Start check
|
||||||
|
resp = client.post(
|
||||||
|
f"{backend_url}/api/compliance/agent/compliance-check",
|
||||||
|
json={
|
||||||
|
"documents": website["documents"],
|
||||||
|
"use_agent": False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if resp.status_code != 200:
|
||||||
|
return {"error": f"Start failed: {resp.status_code}"}
|
||||||
|
|
||||||
|
check_id = resp.json().get("check_id")
|
||||||
|
if not check_id:
|
||||||
|
return {"error": "No check_id"}
|
||||||
|
|
||||||
|
# Poll (max 15 min)
|
||||||
|
for _ in range(300):
|
||||||
|
time.sleep(3)
|
||||||
|
poll = client.get(
|
||||||
|
f"{backend_url}/api/compliance/agent/compliance-check/{check_id}"
|
||||||
|
)
|
||||||
|
if poll.status_code != 200:
|
||||||
|
continue
|
||||||
|
data = poll.json()
|
||||||
|
if data.get("status") == "completed":
|
||||||
|
return data.get("result", {})
|
||||||
|
if data.get("status") == "failed":
|
||||||
|
return {"error": data.get("error", "Check failed")}
|
||||||
|
|
||||||
|
return {"error": "Timeout (15 min)"}
|
||||||
|
|
||||||
|
|
||||||
|
def print_results(all_results: list[tuple[str, dict]]):
|
||||||
|
"""Print comparison table."""
|
||||||
|
print()
|
||||||
|
print("=" * 100)
|
||||||
|
print(f"{'Website':20s} {'Profil':12s} {'DSI L1':10s} {'DSI W':7s} "
|
||||||
|
f"{'Imp L1':10s} {'Dienste':8s} {'Docs':5s} {'Status':12s}")
|
||||||
|
print("-" * 100)
|
||||||
|
|
||||||
|
for name, result in all_results:
|
||||||
|
if "error" in result:
|
||||||
|
print(f"{name:20s} {'ERROR':12s} {result['error'][:60]}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
profile = result.get("business_profile", {})
|
||||||
|
btype = profile.get("business_type", "?").upper()
|
||||||
|
industry = profile.get("industry", "?")
|
||||||
|
services = len(profile.get("detected_services", []))
|
||||||
|
|
||||||
|
docs = result.get("results", [])
|
||||||
|
dsi = next((d for d in docs if d.get("doc_type") == "dse"), {})
|
||||||
|
imp = next((d for d in docs if d.get("doc_type") == "impressum"), {})
|
||||||
|
|
||||||
|
dsi_l1 = f"{dsi.get('completeness_pct', 0)}%"
|
||||||
|
dsi_w = str(dsi.get("word_count", 0))
|
||||||
|
imp_l1 = f"{imp.get('completeness_pct', 0)}%"
|
||||||
|
|
||||||
|
ok_count = sum(1 for d in docs if d.get("completeness_pct", 0) == 100)
|
||||||
|
total = len(docs)
|
||||||
|
|
||||||
|
print(f"{name:20s} {btype+'/'+industry:12s} {dsi_l1:10s} {dsi_w:7s} "
|
||||||
|
f"{imp_l1:10s} {services:8d} {ok_count}/{total:<3d} "
|
||||||
|
f"{'OK' if dsi.get('completeness_pct', 0) == 100 else 'LUECKEN'}")
|
||||||
|
|
||||||
|
print("=" * 100)
|
||||||
|
|
||||||
|
# Detail: all doc results
|
||||||
|
print()
|
||||||
|
for name, result in all_results:
|
||||||
|
if "error" in result:
|
||||||
|
continue
|
||||||
|
docs = result.get("results", [])
|
||||||
|
print(f"--- {name} ---")
|
||||||
|
for d in docs:
|
||||||
|
pct = d.get("completeness_pct", 0)
|
||||||
|
cpct = d.get("correctness_pct", 0)
|
||||||
|
dtype = d.get("doc_type", "?")
|
||||||
|
label = d.get("label", dtype)
|
||||||
|
wc = d.get("word_count", 0)
|
||||||
|
scenario = d.get("scenario", "")
|
||||||
|
checks = d.get("checks", [])
|
||||||
|
l1_total = len([c for c in checks if c.get("level", 1) == 1])
|
||||||
|
l1_pass = len([c for c in checks if c.get("level", 1) == 1 and c.get("passed")])
|
||||||
|
failed = [c["label"] for c in checks if c.get("level", 1) == 1 and not c.get("passed") and not c.get("skipped") and c.get("severity") != "INFO"]
|
||||||
|
print(f" {label:30s} {l1_pass}/{l1_total} L1 ({pct}%) {wc}w {scenario}")
|
||||||
|
if failed:
|
||||||
|
for f in failed[:5]:
|
||||||
|
print(f" ✗ {f[:70]}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--backend-url", default="https://localhost:8002",
|
||||||
|
help="Backend compliance URL")
|
||||||
|
parser.add_argument("--sites", default="all",
|
||||||
|
help="Comma-separated site indices (1-10) or 'all'")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
sites = GT_WEBSITES
|
||||||
|
if args.sites != "all":
|
||||||
|
indices = [int(i) - 1 for i in args.sites.split(",")]
|
||||||
|
sites = [GT_WEBSITES[i] for i in indices if 0 <= i < len(GT_WEBSITES)]
|
||||||
|
|
||||||
|
print(f"Running compliance check on {len(sites)} websites...")
|
||||||
|
print(f"Backend: {args.backend_url}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
all_results = []
|
||||||
|
for i, website in enumerate(sites):
|
||||||
|
name = website["name"]
|
||||||
|
print(f"[{i+1}/{len(sites)}] {name}...", end=" ", flush=True)
|
||||||
|
t0 = time.time()
|
||||||
|
result = run_check(args.backend_url, website)
|
||||||
|
elapsed = time.time() - t0
|
||||||
|
if "error" in result:
|
||||||
|
print(f"ERROR ({elapsed:.0f}s): {result['error'][:60]}")
|
||||||
|
else:
|
||||||
|
docs = result.get("results", [])
|
||||||
|
ok = sum(1 for d in docs if d.get("completeness_pct", 0) == 100)
|
||||||
|
print(f"OK ({elapsed:.0f}s) — {len(docs)} docs, {ok} vollstaendig")
|
||||||
|
all_results.append((name, result))
|
||||||
|
|
||||||
|
print_results(all_results)
|
||||||
|
|
||||||
|
# Save raw results
|
||||||
|
out_file = f"batch_results_{time.strftime('%Y%m%d_%H%M%S')}.json"
|
||||||
|
with open(out_file, "w") as f:
|
||||||
|
json.dump(
|
||||||
|
{name: result for name, result in all_results},
|
||||||
|
f, indent=2, default=str,
|
||||||
|
)
|
||||||
|
print(f"\nRaw results saved to {out_file}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user