fix: Struktureller Fix — Maschinentyp-Filter fuer Keywords + Patterns
PROBLEM: Cobot-Projekt hatte 52 Pressen-Hazards weil Keywords wie "stempel" und "stoessel" ohne Maschinentyp-Kontext matchten. FIX an 3 Stellen: 1. KeywordEntry.MachineTypes — Pressen-Keywords nur fuer press/*_press 2. ParseNarrative(text, machineType) — Parser laedt Maschinentyp aus Projekt 3. HazardPattern.MachineTypes — Pressen-Patterns (HP045-HP058) nur fuer Pressen Verhindert zukuenftig falsche Zuordnungen bei neuen Kundenprojekten. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/breakpilot/ai-compliance-sdk/internal/iace"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ParseNarrativeRequest is the request body for POST /projects/:id/parse-narrative.
|
||||
@@ -43,8 +44,16 @@ func (h *IACEHandler) ParseNarrative(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Parse narrative text deterministically
|
||||
parseResult := iace.ParseNarrative(req.NarrativeText)
|
||||
// Load project to get machine type for context-aware parsing
|
||||
var machineType string
|
||||
if projectID, err := uuid.Parse(c.Param("id")); err == nil {
|
||||
if project, err := h.store.GetProject(c.Request.Context(), projectID); err == nil && project != nil {
|
||||
machineType = project.MachineType
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Parse narrative text deterministically (machine-type-aware)
|
||||
parseResult := iace.ParseNarrative(req.NarrativeText, machineType)
|
||||
|
||||
// 2. Feed parsed tags into pattern engine
|
||||
// Collect all component IDs for tag resolution
|
||||
|
||||
@@ -27,4 +27,9 @@ type HazardPattern struct {
|
||||
ZoneDE string `json:"zone_de,omitempty"` // Gefahrstelle/Zone
|
||||
DefaultSeverity int `json:"default_severity,omitempty"` // 1-5
|
||||
DefaultExposure int `json:"default_exposure,omitempty"` // 1-5
|
||||
// MachineTypes restricts this pattern to specific machine types.
|
||||
// Empty = fires for all machine types. If set, only fires when the
|
||||
// project's machine_type is in this list. Prevents e.g. press-specific
|
||||
// patterns from firing for a cobot project.
|
||||
MachineTypes []string `json:"machine_types,omitempty"`
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ func GetPressHazardPatterns() []HazardPattern {
|
||||
HarmDE: "Toedliche Quetschverletzung, Amputation von Gliedmassen.",
|
||||
AffectedDE: "Einrichter, Bedienpersonal im Werkzeugeinbauraum.",
|
||||
ZoneDE: "Werkzeugeinbauraum unterhalb des Stoessels.",
|
||||
DefaultSeverity: 5, DefaultExposure: 2,
|
||||
DefaultSeverity: 5, DefaultExposure: 2, MachineTypes: []string{"press", "hydraulic_press", "mechanical_press"},
|
||||
},
|
||||
{
|
||||
ID: "HP046", NameDE: "Quetschen im Werkzeugeinbauraum", NameEN: "Crushing in die space",
|
||||
@@ -38,7 +38,7 @@ func GetPressHazardPatterns() []HazardPattern {
|
||||
HarmDE: "Toedliche Quetschverletzung, Amputation der oberen Extremitaeten.",
|
||||
AffectedDE: "Einrichter, Werkzeugbauer, Instandhaltungspersonal.",
|
||||
ZoneDE: "Werkzeugeinbauraum zwischen Ober- und Unterwerkzeug.",
|
||||
DefaultSeverity: 5, DefaultExposure: 3,
|
||||
DefaultSeverity: 5, DefaultExposure: 3, MachineTypes: []string{"press", "hydraulic_press", "mechanical_press"},
|
||||
},
|
||||
{
|
||||
ID: "HP047", NameDE: "Oelnebelexposition Atemwege", NameEN: "Oil mist inhalation exposure",
|
||||
@@ -53,7 +53,7 @@ func GetPressHazardPatterns() []HazardPattern {
|
||||
HarmDE: "Atemwegsreizung, chronische Lungenerkrankung bei Langzeitexposition.",
|
||||
AffectedDE: "Bedienpersonal, Personen im Nahbereich der Presse.",
|
||||
ZoneDE: "Arbeitsbereich rund um die Presse, insbesondere Bedienerseite.",
|
||||
DefaultSeverity: 3, DefaultExposure: 4,
|
||||
DefaultSeverity: 3, DefaultExposure: 4, MachineTypes: []string{"press", "hydraulic_press", "mechanical_press"},
|
||||
},
|
||||
{
|
||||
ID: "HP048", NameDE: "Verbrennung durch heisse Werkstuecke", NameEN: "Burns from hot workpieces",
|
||||
@@ -68,7 +68,7 @@ func GetPressHazardPatterns() []HazardPattern {
|
||||
HarmDE: "Verbrennungen zweiten oder dritten Grades an Haenden und Unterarmen.",
|
||||
AffectedDE: "Bedienpersonal, Einrichter bei Werkzeugwechsel.",
|
||||
ZoneDE: "Entnahmebereich, Werkzeugeinbauraum, Ablagetisch.",
|
||||
DefaultSeverity: 4, DefaultExposure: 3,
|
||||
DefaultSeverity: 4, DefaultExposure: 3, MachineTypes: []string{"press", "hydraulic_press", "mechanical_press"},
|
||||
},
|
||||
{
|
||||
ID: "HP049", NameDE: "Schwebende Last (Hubwerk/Aufzug)", NameEN: "Suspended load (hoist/elevator)",
|
||||
@@ -83,7 +83,7 @@ func GetPressHazardPatterns() []HazardPattern {
|
||||
HarmDE: "Toedliche Verletzung durch herabfallende Last, Knochenbrueche.",
|
||||
AffectedDE: "Personen im Gefahrenbereich unter der schwebenden Last.",
|
||||
ZoneDE: "Bereich unterhalb des Hubwerks, Werkzeugwechselzone.",
|
||||
DefaultSeverity: 5, DefaultExposure: 2,
|
||||
DefaultSeverity: 5, DefaultExposure: 2, MachineTypes: []string{"press", "hydraulic_press", "mechanical_press"},
|
||||
},
|
||||
{
|
||||
ID: "HP050", NameDE: "Einziehen/Scheren Transfersystem", NameEN: "Draw-in/shearing at transfer system",
|
||||
@@ -98,7 +98,7 @@ func GetPressHazardPatterns() []HazardPattern {
|
||||
HarmDE: "Schnittverletzungen, Amputation von Fingern, Quetschungen.",
|
||||
AffectedDE: "Bedienpersonal, Einrichter bei Stoerungsbeseitigung.",
|
||||
ZoneDE: "Transferbereich zwischen den Pressenstationen.",
|
||||
DefaultSeverity: 4, DefaultExposure: 3,
|
||||
DefaultSeverity: 4, DefaultExposure: 3, MachineTypes: []string{"press", "hydraulic_press", "mechanical_press"},
|
||||
},
|
||||
{
|
||||
ID: "HP051", NameDE: "Sturzgefahr Auswurfbereich", NameEN: "Fall hazard at ejection area",
|
||||
@@ -114,7 +114,7 @@ func GetPressHazardPatterns() []HazardPattern {
|
||||
HarmDE: "Knochenbrueche, Prellungen, Kopfverletzungen bei Sturz.",
|
||||
AffectedDE: "Bedienpersonal, Logistikmitarbeiter im Auswurfbereich.",
|
||||
ZoneDE: "Auswurfschacht und angrenzender Bodenbereich.",
|
||||
DefaultSeverity: 3, DefaultExposure: 4,
|
||||
DefaultSeverity: 3, DefaultExposure: 4, MachineTypes: []string{"press", "hydraulic_press", "mechanical_press"},
|
||||
},
|
||||
{
|
||||
ID: "HP052", NameDE: "Druckfreisetzung Hydraulikspeicher", NameEN: "Pressure release from hydraulic accumulator",
|
||||
@@ -129,7 +129,7 @@ func GetPressHazardPatterns() []HazardPattern {
|
||||
HarmDE: "Schwere Schnittverletzungen durch Oelstrahl, Augenverletzungen, Verbrennungen.",
|
||||
AffectedDE: "Instandhaltungspersonal, Hydraulik-Fachkraefte.",
|
||||
ZoneDE: "Hydraulikaggregat, Speicherbereich, Leitungsfuehrung.",
|
||||
DefaultSeverity: 5, DefaultExposure: 2,
|
||||
DefaultSeverity: 5, DefaultExposure: 2, MachineTypes: []string{"press", "hydraulic_press", "mechanical_press"},
|
||||
},
|
||||
{
|
||||
ID: "HP053", NameDE: "Impulslaerm Pressvorgang", NameEN: "Impact noise during press operation",
|
||||
@@ -144,7 +144,7 @@ func GetPressHazardPatterns() []HazardPattern {
|
||||
HarmDE: "Laermschwerhoerigkeit, Tinnitus bei Langzeitexposition.",
|
||||
AffectedDE: "Bedienpersonal, Personen in angrenzenden Arbeitsbereichen.",
|
||||
ZoneDE: "Gesamter Pressenbereich, Radius ca. 5-10 m um die Maschine.",
|
||||
DefaultSeverity: 3, DefaultExposure: 5,
|
||||
DefaultSeverity: 3, DefaultExposure: 5, MachineTypes: []string{"press", "hydraulic_press", "mechanical_press"},
|
||||
},
|
||||
{
|
||||
ID: "HP054", NameDE: "Schwungrad-Restenergie nach Abschaltung", NameEN: "Flywheel residual energy after shutdown",
|
||||
@@ -159,7 +159,7 @@ func GetPressHazardPatterns() []HazardPattern {
|
||||
HarmDE: "Erfassen durch rotierende Teile, schwere Schnittverletzungen, Skalpierung.",
|
||||
AffectedDE: "Instandhaltungspersonal, Einrichter nach Maschinenstopp.",
|
||||
ZoneDE: "Schwungradbereich, Kupplungsraum, Antriebsseite der Presse.",
|
||||
DefaultSeverity: 4, DefaultExposure: 2,
|
||||
DefaultSeverity: 4, DefaultExposure: 2, MachineTypes: []string{"press", "hydraulic_press", "mechanical_press"},
|
||||
},
|
||||
{
|
||||
ID: "HP055", NameDE: "Umgehung Schutzeinrichtung (Pressentuer)", NameEN: "Bypass of safety guard (press door)",
|
||||
@@ -174,7 +174,7 @@ func GetPressHazardPatterns() []HazardPattern {
|
||||
HarmDE: "Toedliche Quetsch- oder Scherverletzungen bei Eingriff in den Gefahrenbereich.",
|
||||
AffectedDE: "Bedienpersonal, Einrichter bei Stoerungsbeseitigung.",
|
||||
ZoneDE: "Gesamter Werkzeugeinbauraum hinter der Schutztuer.",
|
||||
DefaultSeverity: 5, DefaultExposure: 3,
|
||||
DefaultSeverity: 5, DefaultExposure: 3, MachineTypes: []string{"press", "hydraulic_press", "mechanical_press"},
|
||||
},
|
||||
{
|
||||
ID: "HP056", NameDE: "Fehlbedienung Zweihandschaltung", NameEN: "Two-hand control misoperation",
|
||||
@@ -189,7 +189,7 @@ func GetPressHazardPatterns() []HazardPattern {
|
||||
HarmDE: "Quetschverletzungen der freien Hand im Werkzeugbereich.",
|
||||
AffectedDE: "Bedienpersonal an der Pressenbedienung.",
|
||||
ZoneDE: "Gefahrenbereich zwischen Ober- und Unterwerkzeug.",
|
||||
DefaultSeverity: 5, DefaultExposure: 3,
|
||||
DefaultSeverity: 5, DefaultExposure: 3, MachineTypes: []string{"press", "hydraulic_press", "mechanical_press"},
|
||||
},
|
||||
{
|
||||
ID: "HP057", NameDE: "Hydraulikoelleckage + Rutschgefahr", NameEN: "Hydraulic oil leakage + slip hazard",
|
||||
@@ -204,7 +204,7 @@ func GetPressHazardPatterns() []HazardPattern {
|
||||
HarmDE: "Sturzverletzungen durch Ausrutschen, Hautreizungen bei Hautkontakt.",
|
||||
AffectedDE: "Bedienpersonal, Logistikmitarbeiter, alle Personen im Pressenbereich.",
|
||||
ZoneDE: "Bodenbereich rund um das Hydraulikaggregat und unter der Presse.",
|
||||
DefaultSeverity: 2, DefaultExposure: 4,
|
||||
DefaultSeverity: 2, DefaultExposure: 4, MachineTypes: []string{"press", "hydraulic_press", "mechanical_press"},
|
||||
},
|
||||
{
|
||||
ID: "HP058", NameDE: "Ergonomische Belastung Kistenwechsel", NameEN: "Ergonomic strain during bin changeover",
|
||||
@@ -219,7 +219,7 @@ func GetPressHazardPatterns() []HazardPattern {
|
||||
HarmDE: "Rueckenverletzungen, Bandscheibenvorfall, Muskel-Skelett-Erkrankungen.",
|
||||
AffectedDE: "Bedienpersonal, Logistikmitarbeiter an der Presse.",
|
||||
ZoneDE: "Auswurfbereich, Palettenstellplatz neben der Presse.",
|
||||
DefaultSeverity: 2, DefaultExposure: 5,
|
||||
DefaultSeverity: 2, DefaultExposure: 5, MachineTypes: []string{"press", "hydraulic_press", "mechanical_press"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@ type KeywordEntry struct {
|
||||
ComponentIDs []string // Matched component library IDs (C001-C135)
|
||||
EnergyIDs []string // Matched energy source IDs (EN01-EN20)
|
||||
ExtraTags []string // Additional tags derived from keyword context
|
||||
// MachineTypes restricts this keyword to specific machine types.
|
||||
// Empty = matches all machine types. If set, only matches when the
|
||||
// project's machine_type is in this list.
|
||||
MachineTypes []string // e.g. ["press", "hydraulic_press"]
|
||||
}
|
||||
|
||||
// GetKeywordDictionary returns the complete keyword dictionary for
|
||||
@@ -14,13 +18,13 @@ type KeywordEntry struct {
|
||||
// machinery terminology in German and English.
|
||||
func GetKeywordDictionary() []KeywordEntry {
|
||||
return []KeywordEntry{
|
||||
// ── Pressen / Umformmaschinen ───────────────────────────────────
|
||||
{Keywords: []string{"presse", "press", "umform", "umformung"}, ComponentIDs: []string{"C008", "C122"}, EnergyIDs: []string{"EN01"}, ExtraTags: []string{"high_force", "crush_point"}},
|
||||
{Keywords: []string{"kniehebel", "toggle"}, ComponentIDs: []string{"C121"}, ExtraTags: []string{"mechanical_transmission"}},
|
||||
{Keywords: []string{"stossel", "stoessel", "ram", "slide"}, ComponentIDs: []string{"C122"}, EnergyIDs: []string{"EN01"}, ExtraTags: []string{"moving_part", "crush_point", "gravity_risk"}},
|
||||
{Keywords: []string{"stempel", "punch", "matrize", "die"}, ComponentIDs: []string{"C126"}, ExtraTags: []string{"crush_point", "cutting_part"}},
|
||||
{Keywords: []string{"schwungrad", "flywheel"}, ComponentIDs: []string{"C133"}, EnergyIDs: []string{"EN02", "EN03"}, ExtraTags: []string{"stored_energy", "rotating_part"}},
|
||||
{Keywords: []string{"werkzeugeinbauraum", "die space"}, ComponentIDs: []string{"C132"}, ExtraTags: []string{"crush_point", "pinch_point"}},
|
||||
// ── Pressen / Umformmaschinen (NUR fuer press/hydraulic_press) ──
|
||||
{Keywords: []string{"presse", "press", "umform", "umformung"}, ComponentIDs: []string{"C008", "C122"}, EnergyIDs: []string{"EN01"}, ExtraTags: []string{"high_force", "crush_point"}, MachineTypes: []string{"press", "hydraulic_press", "mechanical_press", "stamping_press"}},
|
||||
{Keywords: []string{"kniehebel", "toggle"}, ComponentIDs: []string{"C121"}, ExtraTags: []string{"mechanical_transmission"}, MachineTypes: []string{"press"}},
|
||||
{Keywords: []string{"stossel", "stoessel", "ram", "slide"}, ComponentIDs: []string{"C122"}, EnergyIDs: []string{"EN01"}, ExtraTags: []string{"moving_part", "crush_point", "gravity_risk"}, MachineTypes: []string{"press", "hydraulic_press", "mechanical_press"}},
|
||||
{Keywords: []string{"stempel", "punch", "matrize", "die"}, ComponentIDs: []string{"C126"}, ExtraTags: []string{"crush_point", "cutting_part"}, MachineTypes: []string{"press", "hydraulic_press", "mechanical_press", "stamping_press"}},
|
||||
{Keywords: []string{"schwungrad", "flywheel"}, ComponentIDs: []string{"C133"}, EnergyIDs: []string{"EN02", "EN03"}, ExtraTags: []string{"stored_energy", "rotating_part"}, MachineTypes: []string{"press", "mechanical_press"}},
|
||||
{Keywords: []string{"werkzeugeinbauraum", "die space"}, ComponentIDs: []string{"C132"}, ExtraTags: []string{"crush_point", "pinch_point"}, MachineTypes: []string{"press", "hydraulic_press", "mechanical_press"}},
|
||||
|
||||
// ── Foerdertechnik ──────────────────────────────────────────────
|
||||
{Keywords: []string{"foerderband", "transportband", "conveyor"}, ComponentIDs: []string{"C003"}, EnergyIDs: []string{"EN01", "EN02"}, ExtraTags: []string{"entanglement_risk"}},
|
||||
|
||||
@@ -94,7 +94,9 @@ var roleKeywords = map[string]string{
|
||||
// ParseNarrative extracts components, energy sources, lifecycle phases,
|
||||
// roles, and tags from a machine description text. Fully deterministic,
|
||||
// no LLM required.
|
||||
func ParseNarrative(text string) ParseResult {
|
||||
// machineType is optional — if provided, keywords with MachineTypes
|
||||
// restrictions are only matched when the machine type is in the list.
|
||||
func ParseNarrative(text string, machineType ...string) ParseResult {
|
||||
result := ParseResult{}
|
||||
if text == "" {
|
||||
return result
|
||||
@@ -122,7 +124,27 @@ func ParseNarrative(text string) ParseResult {
|
||||
seenEnergy := make(map[string]bool)
|
||||
tagSet := make(map[string]bool)
|
||||
|
||||
// Resolve machine type for filtering
|
||||
var mType string
|
||||
if len(machineType) > 0 {
|
||||
mType = machineType[0]
|
||||
}
|
||||
|
||||
for _, entry := range dictionary {
|
||||
// Skip keywords restricted to other machine types
|
||||
if len(entry.MachineTypes) > 0 && mType != "" {
|
||||
matched := false
|
||||
for _, mt := range entry.MachineTypes {
|
||||
if mt == mType {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
continue // This keyword is for a different machine type
|
||||
}
|
||||
}
|
||||
|
||||
for _, kw := range entry.Keywords {
|
||||
kwNorm := strings.ToLower(kw)
|
||||
kwNorm = strings.ReplaceAll(kwNorm, "ä", "ae")
|
||||
|
||||
Reference in New Issue
Block a user