package iace import "strings" // Capability-Domain-Gating — the cure for cross-domain leakage. // // Many domain-specific hazard patterns were authored gated only by a GENERIC // capability tag (e.g. "rotating_part"), so they fire for every machine that // has rotating parts — a lift, a robot cell — even though the hazard belongs to // a press, a spinning machine or a PV array. This is the precision-killing // inverse of ghost patterns; both stem from inconsistent applicability. // // The fix is capability-driven (NOT a machine-type whitelist hack): a pattern // whose OWN scenario text names a foreign machine gets that domain's capability // tag appended to its RequiredComponentTags. The same tag is emitted by the // domain's narrative keywords (keyword_dictionary.go), so the pattern still // fires for its real domain but no longer leaks into unrelated machines. // // INVARIANT: every tag below MUST be emittable via keyword_dictionary.go, // otherwise the gated pattern becomes a ghost. TestTagVocabulary_GhostPatterns // is the regression guard for this. // domainGateTerms maps a machine-betraying term (umlaut-normalised, lowercase) // to the domain capability tag that gates patterns mentioning it. var domainGateTerms = map[string]string{ // Pressen / Stanzen / Umformen "stanzhub": "dom_press", "pressenhub": "dom_press", "pressenstoessel": "dom_press", "dauerhub": "dom_press", "exzenterpresse": "dom_press", "beinpresse": "dom_press", "stanzpresse": "dom_press", "umformpresse": "dom_press", "pressenteil": "dom_press", "pressraum": "dom_press", "blechbearbeitung": "dom_press", "werkzeugraum der presse": "dom_press", // Glas-Bearbeitung "glasschneid": "dom_glass", "glasbearbeitung": "dom_glass", "glasscheibe": "dom_glass", "glaskante": "dom_glass", // Kunststoff / Spritzguss / Extrusion "spritzgie": "dom_plastics", "extruder": "dom_plastics", "extrusion": "dom_plastics", "kunststoffschmelze": "dom_plastics", "schliesseinheit": "dom_plastics", // Walzen / Kalander / Laminieren "walzenspalt": "dom_rolling", "zweiwalzenwerk": "dom_rolling", "kalander": "dom_rolling", "walzwerk": "dom_rolling", "laminieranlage": "dom_rolling", "laminier": "dom_rolling", // Textil "spinnmaschine": "dom_textile", "webmaschine": "dom_textile", "spinnerei": "dom_textile", // Schleifen "schleifscheibe": "dom_grinding", "schleifbock": "dom_grinding", // Schweissen "widerstandsschweiss": "dom_welding", "lichtbogenschweiss": "dom_welding", "schutzgasschweiss": "dom_welding", "punktschweiss": "dom_welding", "schweisselektrod": "dom_welding", "elektrodenspalt": "dom_welding", // Solar / PV "pv-modul": "dom_solar", "photovoltaik": "dom_solar", "pv-anlage": "dom_solar", "dc-steckverbindung": "dom_solar", "solarmodul": "dom_solar", // Windkraft "gondel": "dom_wind", "rotorblatt": "dom_wind", "windenergieanlage": "dom_wind", // CNC / Zerspanung "drehmaschine": "dom_cnc", "fraesmaschine": "dom_cnc", // Landwirtschaft "maehdrescher": "dom_agri", "ballenpresse": "dom_agri", "feldhaecksler": "dom_agri", // Roll-/Fahrtreppe "rolltreppe": "dom_escalator", "fahrtreppe": "dom_escalator", // Aussen-/Witterungs-/Bioarbeit (Forst, Bau im Freien) "zecke": "dom_outdoor", "zeckenstich": "dom_outdoor", "fsme": "dom_outdoor", "borreliose": "dom_outdoor", "im freien": "dom_outdoor", "freigelaende": "dom_outdoor", "aussengelaende": "dom_outdoor", "ausseneinsatz": "dom_outdoor", "witterung": "dom_outdoor", "winterarbeit": "dom_outdoor", "nagerkot": "dom_outdoor", "hantavirus": "dom_outdoor", // Lueftung/Feuchte (Schimmel) "schimmel": "dom_ventilation", "schimmelspor": "dom_ventilation", "lueftungsanlage": "dom_ventilation", "lueftungskanal": "dom_ventilation", // Zerspanung / Kuehlschmierstoff "kuehlschmierstoff": "dom_machining", "kss-kreislauf": "dom_machining", "kss-aufbereitung": "dom_machining", "kuehlturm": "dom_machining", "bearbeitungszentrum": "dom_machining", // Schuettgut / Silo / Gaerbehaelter (Confined Space mit Schuettgut) "silo": "dom_bulk", "schuettgut": "dom_bulk", "gaerbehaelter": "dom_bulk", "getreidesilo": "dom_bulk", "mehlsilo": "dom_bulk", // Palettierer "palettierer": "dom_palletizer", "palettieranlage": "dom_palletizer", // Spielplatz / Spielgeraet "klettergeraet": "dom_playground", "spielplatz": "dom_playground", "spielgeraet": "dom_playground", // Fitness / Kraftgeraet "gewichtstapel": "dom_fitness", "langhantel": "dom_fitness", "bankdrueck": "dom_fitness", "kniebeug": "dom_fitness", "kraftstation": "dom_fitness", // Schwimmbad / Aquatik (Entrapment, Nassbereich-Strom, Beckenrand) "schwimmbecken": "dom_aquatic", "schwimmbad": "dom_aquatic", "schwimmhalle": "dom_aquatic", "beckenumrandung": "dom_aquatic", "beckenrand": "dom_aquatic", "massageduese": "dom_aquatic", "badegaeste": "dom_aquatic", "sprungturm": "dom_aquatic", "schwimmbadtechnik": "dom_aquatic", // Fahrgeschaeft / Vergnuegungspark "karussell": "dom_amusement", "fahrgeschaeft": "dom_amusement", "riesenrad": "dom_amusement", "achterbahn": "dom_amusement", // Mobile Maschine mit Fahrerstand (Ganzkoerpervibration etc.) "fahrersitz": "dom_mobile_cab", "fahrerkabine": "dom_mobile_cab", "fahrerstand": "dom_mobile_cab", "fahrerhaus": "dom_mobile_cab", // Lackieren / Beschichten (Loesemittel, ESD-Zuendung Lackierzone) "lackier": "dom_coating", "loesemitteldampf": "dom_coating", "pulverbeschicht": "dom_coating", "spritzlackier": "dom_coating", // Ex-Prozessanlage / Tanklager "tanklager": "dom_exproc", "raffinerie": "dom_exproc", "tankfarm": "dom_exproc", // Chemie-Reaktor / Mischanlage "reaktor": "dom_chem", "mischbereich": "dom_chem", "exotherme reaktion": "dom_chem", "ruehrkessel": "dom_chem", // Sauerstoff-/Gasversorgungsanlage "sauerstoffanreicherung": "dom_o2", "sauerstoff-versorgung": "dom_o2", // Drehmaschine / Zerspanung (Spannfutter, Spaeneflug, Spindelumgebung) "drehfutter": "dom_cnc", "spannfutterbacke": "dom_cnc", "spannbacke": "dom_cnc", "spaeneflug": "dom_cnc", "spanflug": "dom_cnc", "spindelumgebung": "dom_cnc", "werkzeugmaschine": "dom_cnc", // Roboter / Cobot (ungated Roboterzellen-Hazards) "roboterzelle": "dom_robot", "roboterarm": "dom_robot", "roboter-arbeitsraum": "dom_robot", "schwenkbereich roboter": "dom_robot", "knickarmroboter": "dom_robot", "teach-zone": "dom_robot", // Saege (Bandsaege, Gattersaege) "bandsaege": "dom_sawing", "saegeband": "dom_sawing", "gattersaege": "dom_sawing", // Folien-/Karton-Konfektionierung (Wickler, Trennmesser) "folienwickler": "dom_converting", "folieneinlauf": "dom_converting", "wickelachse": "dom_converting", "folientrennbereich": "dom_converting", "kartonschneider": "dom_converting", // Kunststoff Blasformen (ergaenzt dom_plastics) "blasformwerkzeug": "dom_plastics", "blasstation": "dom_plastics", "blasform": "dom_plastics", // Textil-Zuschnitt / Konfektionierung (ergaenzt dom_textile) "stoffauflage": "dom_textile", "konfektionierung": "dom_textile", "schneidkopfbereich": "dom_textile", // Abgelegener / untertage Einzelarbeitsplatz (kein Notruf-Empfang) "kein empfang": "dom_remote", "unterirdisch": "dom_remote", "untertage": "dom_remote", // Asbest-Altanlagen "asbest": "dom_asbestos", // Spielplatz-Schaukel (ergaenzt dom_playground: Kettenglied-Fingerfang) "schaukelkette": "dom_playground", "nestschaukel": "dom_playground", "schaukelsitz": "dom_playground", } // applyDomainGates appends a domain capability tag to every pattern whose own // text betrays that domain, so domain-specific hazards stop leaking into // unrelated machines. Idempotent; safe to run once after pattern collection. func applyDomainGates(patterns []HazardPattern) []HazardPattern { for i := range patterns { text := normalizeGateText(patterns[i].NameDE + " " + patterns[i].ScenarioDE + " " + patterns[i].TriggerDE + " " + patterns[i].HarmDE + " " + patterns[i].ZoneDE) present := make(map[string]bool, len(patterns[i].RequiredComponentTags)) for _, t := range patterns[i].RequiredComponentTags { present[t] = true } for term, tag := range domainGateTerms { if present[tag] { continue } if strings.Contains(text, term) { patterns[i].RequiredComponentTags = append(patterns[i].RequiredComponentTags, tag) present[tag] = true } } } return patterns } // normalizeGateText lowercases and folds umlauts, matching keyword_dictionary's // normalisation so gate terms and emit keywords use one vocabulary. func normalizeGateText(s string) string { s = strings.ToLower(s) s = strings.ReplaceAll(s, "ä", "ae") s = strings.ReplaceAll(s, "ö", "oe") s = strings.ReplaceAll(s, "ü", "ue") s = strings.ReplaceAll(s, "ß", "ss") return s }