Compare commits
41 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 |
@@ -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)')
|
||||||
}
|
}
|
||||||
|
|||||||
+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
|
||||||
|
|||||||
@@ -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,11 +209,8 @@ func (h *IACEHandler) InitializeProject(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Join applicable lifecycles for the LifecyclePhase field
|
// Join all applicable lifecycles as comma-separated string
|
||||||
lifecycleStr := ""
|
lifecycleStr := strings.Join(mp.ApplicableLifecycles, ",")
|
||||||
if len(mp.ApplicableLifecycles) > 0 {
|
|
||||||
lifecycleStr = mp.ApplicableLifecycles[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
hz, cerr := h.store.CreateHazard(ctx, iace.CreateHazardRequest{
|
hz, cerr := h.store.CreateHazard(ctx, iace.CreateHazardRequest{
|
||||||
ProjectID: projectID,
|
ProjectID: projectID,
|
||||||
@@ -227,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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,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)
|
||||||
@@ -255,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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
rt := iace.ReductionType(entry.ReductionType)
|
rt := iace.ReductionType(entry.ReductionType)
|
||||||
if rt == "" {
|
if rt == "" {
|
||||||
rt = iace.ReductionTypeInformation
|
rt = iace.ReductionTypeInformation
|
||||||
}
|
}
|
||||||
_, cerr := h.store.CreateMitigation(ctx, iace.CreateMitigationRequest{
|
_, cerr := h.store.CreateMitigation(ctx, iace.CreateMitigationRequest{
|
||||||
HazardID: hazardID, ReductionType: rt,
|
HazardID: hazID, ReductionType: rt,
|
||||||
Name: entry.Name, Description: entry.Description,
|
Name: entry.Name, Description: entry.Description,
|
||||||
})
|
})
|
||||||
if cerr == nil {
|
if cerr != nil {
|
||||||
|
fmt.Printf("MEASURE-ERROR: mid=%s name=%s err=%v\n", mid, entry.Name, cerr)
|
||||||
|
} else {
|
||||||
created++
|
created++
|
||||||
usedMeasureIDs[sm.MeasureID] = true
|
added++
|
||||||
|
usedIDs[mid] = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for hazCat, hazID := range hazardIDsByCategory {
|
// Priority 2: Category fallback (skip already-used IDs)
|
||||||
measCat := patternCatToMeasureCat(hazCat)
|
|
||||||
added := 0
|
|
||||||
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 == "" {
|
||||||
@@ -297,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 {
|
||||||
@@ -76,6 +36,8 @@ func CompareBenchmark(gt *GroundTruth, hazards []Hazard, mitigations []Mitigatio
|
|||||||
Scenario: h.Scenario,
|
Scenario: h.Scenario,
|
||||||
PossibleHarm: h.PossibleHarm,
|
PossibleHarm: h.PossibleHarm,
|
||||||
TriggerEvent: h.TriggerEvent,
|
TriggerEvent: h.TriggerEvent,
|
||||||
|
AffectedPerson: h.AffectedPerson,
|
||||||
|
LifecyclePhase: h.LifecyclePhase,
|
||||||
Mitigations: mitNamesByHazard[h.ID.String()],
|
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
|
||||||
|
}
|
||||||
|
|
||||||
@@ -102,6 +102,8 @@ type HazardSummary struct {
|
|||||||
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"`
|
||||||
|
AffectedPerson string `json:"affected_person,omitempty"`
|
||||||
|
LifecyclePhase string `json:"lifecycle_phase,omitempty"`
|
||||||
Mitigations []string `json:"mitigations,omitempty"`
|
Mitigations []string `json:"mitigations,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -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"},
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ 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"},
|
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.",
|
TriggerDE: "Roboterarm bewegt sich waehrend Person im Gefahrenbereich steht.",
|
||||||
@@ -31,7 +31,7 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
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"},
|
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.",
|
||||||
@@ -45,7 +45,7 @@ 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"},
|
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.",
|
||||||
@@ -59,15 +59,29 @@ 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,
|
||||||
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "changeover", "fault_clearing"},
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "changeover", "fault_clearing"},
|
||||||
ScenarioDE: "Roboterarm ueberschreitet den vorgesehenen Bewegungsbereich und trifft den Schutzzaun mit hoher Kraft.",
|
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.",
|
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
|
||||||
// ================================================================
|
// ================================================================
|
||||||
@@ -76,7 +90,7 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
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"},
|
||||||
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "changeover", "fault_clearing"},
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "changeover", "fault_clearing"},
|
||||||
ScenarioDE: "Person greift in den Bereich des Greifers. Hand wird zwischen Greifbacken und Werkstueck eingeklemmt.",
|
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.",
|
||||||
@@ -90,9 +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,
|
||||||
ApplicableLifecycles: []string{"normal_operation", "setup", "changeover"},
|
ApplicableLifecycles: []string{"normal_operation", "setup", "changeover"},
|
||||||
ScenarioDE: "Greifer verliert das Werkstueck waehrend des Transports (Druckverlust, oelige Oberflaeche, falsches Werkstueck).",
|
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",
|
||||||
@@ -104,9 +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,
|
||||||
ApplicableLifecycles: []string{"normal_operation"},
|
ApplicableLifecycles: []string{"normal_operation"},
|
||||||
ScenarioDE: "Greifer versagt und Roboterarm beschleunigt das freigesetzte Werkstueck in Richtung Schutzzaun oder Einhausung.",
|
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",
|
||||||
@@ -121,7 +135,7 @@ 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,
|
||||||
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "maintenance", "fault_clearing"},
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "maintenance", "fault_clearing"},
|
||||||
ScenarioDE: "Person greift an Foerderband und wird zwischen beweglichen und feststehenden Teilen eingeklemmt.",
|
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.",
|
||||||
@@ -135,7 +149,7 @@ 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"},
|
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.",
|
||||||
@@ -149,9 +163,9 @@ 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,
|
||||||
ApplicableLifecycles: []string{"normal_operation", "setup"},
|
ApplicableLifecycles: []string{"normal_operation", "setup"},
|
||||||
ScenarioDE: "Werkstueck faehrt ueber das Ende des Transportbandes hinaus und faellt herab.",
|
ScenarioDE: "Werkstueck faehrt ueber das Ende des Transportbandes hinaus, faellt herab und trifft Person am Be-/Entladeplatz.",
|
||||||
TriggerDE: "Mechanischer Anschlag fehlt oder ist beschaedigt.",
|
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",
|
||||||
@@ -166,7 +180,7 @@ 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,
|
||||||
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "maintenance", "fault_clearing"},
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "maintenance", "fault_clearing"},
|
||||||
ScenarioDE: "Person schneidet sich an nicht entgrateten oder scharfkantigen Blechen der Einhausung oder Verkleidung.",
|
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.",
|
||||||
@@ -182,8 +196,8 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
ID: "HP1630", NameDE: "Pneumatikschlauch springt unter Druck 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"},
|
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.",
|
||||||
@@ -196,12 +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,
|
||||||
ApplicableLifecycles: []string{"maintenance", "fault_clearing", "changeover"},
|
ApplicableLifecycles: []string{"maintenance", "fault_clearing", "changeover"},
|
||||||
ScenarioDE: "Pneumatik-Komponenten stehen nach Abschaltung noch unter Druck. Bei Arbeiten an der Anlage werden druckbeaufschlagte Teile geloest.",
|
ScenarioDE: "Person loest druckbeaufschlagte Pneumatik-Komponenten die nach Abschaltung noch unter Druck stehen. Teile fliegen unkontrolliert weg und treffen die Person.",
|
||||||
TriggerDE: "Fehlende Druckentlastung. Gesperrte Rueckschlagventile halten Druck.",
|
TriggerDE: "Fehlende Druckentlastung. Gesperrte Rueckschlagventile halten Druck.",
|
||||||
HarmDE: "Unkontrolliertes Loesen von Verbindungen, wegfliegende Teile.",
|
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,
|
||||||
@@ -209,14 +223,56 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
// ================================================================
|
// ================================================================
|
||||||
// Kuehlschmierstoff (KSS)
|
// Kuehlschmierstoff (KSS)
|
||||||
// ================================================================
|
// ================================================================
|
||||||
|
{
|
||||||
|
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",
|
ID: "HP1635", NameDE: "Ausrutschen durch KSS-Leckage", NameEN: "Slipping due to coolant leakage",
|
||||||
RequiredComponentTags: []string{},
|
RequiredComponentTags: []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"},
|
||||||
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "maintenance", "fault_clearing"},
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "maintenance", "fault_clearing"},
|
||||||
ScenarioDE: "Kuehlschmierstoff tritt aus undichter Leitung oder Verbindung aus und bildet einen rutschigen Belag auf dem Boden.",
|
ScenarioDE: "Kuehlschmierstoff tritt aus und bildet rutschigen Belag auf dem Boden. Person rutscht aus und stuerzt.",
|
||||||
TriggerDE: "Leckage an Schlauchverbindung, Dichtungsversagen.",
|
TriggerDE: "Leckage an Schlauchverbindung, Dichtungsversagen.",
|
||||||
HarmDE: "Ausrutschen und Sturz, Prellungen, Knochenbrueche.",
|
HarmDE: "Ausrutschen und Sturz, Prellungen, Knochenbrueche.",
|
||||||
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
AffectedDE: "Bedienpersonal, Wartungspersonal",
|
||||||
@@ -228,7 +284,7 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
RequiredComponentTags: []string{},
|
RequiredComponentTags: []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"},
|
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.",
|
TriggerDE: "Hautkontakt beim Reinigen, Werkzeugwechsel oder Beseitigung von Stoerungen.",
|
||||||
@@ -242,11 +298,11 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
RequiredComponentTags: []string{},
|
RequiredComponentTags: []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", "maintenance"},
|
ApplicableLifecycles: []string{"normal_operation", "setup", "maintenance"},
|
||||||
ScenarioDE: "Waehrend der Werkstueckbearbeitung entstehen KSS-Aerosole die beim Oeffnen der Bearbeitungszelle freigesetzt werden.",
|
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.",
|
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,
|
||||||
@@ -257,14 +313,14 @@ 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,
|
||||||
ApplicableLifecycles: []string{"normal_operation", "setup", "maintenance", "fault_clearing"},
|
ApplicableLifecycles: []string{"normal_operation", "setup", "maintenance", "fault_clearing"},
|
||||||
ScenarioDE: "Person beruehrt spannungsfuehrende Teile der Anlage die nicht ausreichend isoliert oder abgedeckt sind.",
|
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,
|
||||||
@@ -274,10 +330,10 @@ func GetRobotCellPatterns() []HazardPattern {
|
|||||||
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,
|
||||||
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "maintenance", "fault_clearing"},
|
ApplicableLifecycles: []string{"normal_operation", "setup", "cleaning", "maintenance", "fault_clearing"},
|
||||||
ScenarioDE: "Schutzleiter ist unterbrochen oder nicht korrekt angeschlossen. Beruehrbare leitfaehige Teile fuehren gefaehrliche Beruehrungsspannung.",
|
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",
|
||||||
@@ -290,9 +346,9 @@ 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,
|
||||||
ApplicableLifecycles: []string{"normal_operation", "setup", "maintenance"},
|
ApplicableLifecycles: []string{"normal_operation", "setup", "maintenance"},
|
||||||
ScenarioDE: "Kabelquerschnitt ist nicht auf die maximale Leistung ausgelegt oder Ueberstromschutz fehlt. Kabel ueberhitzt und entzuendet sich.",
|
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.",
|
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",
|
||||||
|
|||||||
@@ -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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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",
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -67,6 +67,7 @@ type PatternMatch struct {
|
|||||||
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"`
|
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.
|
||||||
@@ -220,6 +221,7 @@ func (e *PatternEngine) Match(input MatchInput) *MatchOutput {
|
|||||||
GeneratedHazardType: p.GeneratedHazardType,
|
GeneratedHazardType: p.GeneratedHazardType,
|
||||||
MatchedFailureModes: matchedFMs,
|
MatchedFailureModes: matchedFMs,
|
||||||
ApplicableLifecycles: p.ApplicableLifecycles,
|
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,22 +380,54 @@ 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 not docs:
|
if docs:
|
||||||
return ""
|
texts = []
|
||||||
doc = docs[0]
|
for doc in docs:
|
||||||
return doc.get("full_text", "") or doc.get("text_preview", "") or ""
|
t = 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)
|
||||||
|
|
||||||
|
# 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 ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
@@ -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 |
|
||||||
|
|||||||
@@ -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