feat: IACE deterministic narrative parser + library extensions
Library Extensions: - 15 new components (C121-C135): knee lever, hydraulic ram, lubrication system, extraction system, vibrating plate, die tooling, transfer system, hoist, chute, oil drip tray, pressure relief valve, die space, flywheel, bin changeover station, inspection scale - 8 new tags: person_under_load, two_hand_control_required, thermal_accumulation, mechanical_transmission, oil_mist_risk, rapid_energy_release, gravity_suspended_load, bypass_risk - 14 new patterns (HP045-HP058): ram drop, die space crushing, oil mist inhalation, hot workpiece burns, suspended load, transfer draw-in, ejection fall, accumulator pressure release, impact noise, flywheel residual energy, guard bypass, two-hand misoperation, oil leakage, ergonomic bin changeover Deterministic Parser (NO LLM): - keyword_dictionary.go: ~100 entries mapping DE/EN keywords to component IDs, energy source IDs, and tags - narrative_parser.go: ParseNarrative() extracts components, energy sources, lifecycle phases, roles, tech specs, and context tags from free-text machine descriptions via keyword matching + regex - Tech spec regex: extracts kN, V, °C, bar, kW, rpm values and derives energy sources + severity tags automatically - iace_handler_parser.go: POST /projects/:id/parse-narrative endpoint chains parser → pattern engine → hazard suggestions Test: Paste Kniehebelpresse description → should detect 10+ components, 15+ hazards, all deterministically without LLM. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,99 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/breakpilot/ai-compliance-sdk/internal/iace"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseNarrativeRequest is the request body for POST /projects/:id/parse-narrative.
|
||||||
|
type ParseNarrativeRequest struct {
|
||||||
|
NarrativeText string `json:"narrative_text" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseNarrativeResponse contains the deterministic parsing results.
|
||||||
|
type ParseNarrativeResponse struct {
|
||||||
|
Components []iace.ComponentMatch `json:"components"`
|
||||||
|
EnergySources []iace.EnergyMatch `json:"energy_sources"`
|
||||||
|
LifecyclePhases []string `json:"lifecycle_phases"`
|
||||||
|
Roles []string `json:"roles"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
TechSpecs []iace.TechSpec `json:"tech_specs"`
|
||||||
|
Confidence float64 `json:"confidence"`
|
||||||
|
// Pattern match results (from feeding parsed data into pattern engine)
|
||||||
|
MatchedPatterns int `json:"matched_patterns"`
|
||||||
|
SuggestedHazards []struct {
|
||||||
|
Category string `json:"category"`
|
||||||
|
PatternID string `json:"pattern_id"`
|
||||||
|
PatternName string `json:"pattern_name"`
|
||||||
|
Priority int `json:"priority"`
|
||||||
|
} `json:"suggested_hazards"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseNarrative handles POST /projects/:id/parse-narrative
|
||||||
|
// Deterministically extracts components, energy sources, lifecycle phases,
|
||||||
|
// roles, and hazard patterns from a free-text machine description.
|
||||||
|
// NO LLM required — pure keyword matching + pattern engine.
|
||||||
|
func (h *IACEHandler) ParseNarrative(c *gin.Context) {
|
||||||
|
var req ParseNarrativeRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "narrative_text is required"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Parse narrative text deterministically
|
||||||
|
parseResult := iace.ParseNarrative(req.NarrativeText)
|
||||||
|
|
||||||
|
// 2. Feed parsed tags into pattern engine
|
||||||
|
// Collect all component IDs for tag resolution
|
||||||
|
var componentIDs []string
|
||||||
|
for _, comp := range parseResult.Components {
|
||||||
|
componentIDs = append(componentIDs, comp.LibraryID)
|
||||||
|
}
|
||||||
|
var energyIDs []string
|
||||||
|
for _, e := range parseResult.EnergySources {
|
||||||
|
energyIDs = append(energyIDs, e.SourceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run pattern matching via PatternEngine
|
||||||
|
engine := iace.NewPatternEngine()
|
||||||
|
matchInput := iace.MatchInput{
|
||||||
|
ComponentLibraryIDs: componentIDs,
|
||||||
|
EnergySourceIDs: energyIDs,
|
||||||
|
LifecyclePhases: parseResult.LifecyclePhases,
|
||||||
|
CustomTags: parseResult.CustomTags,
|
||||||
|
}
|
||||||
|
matchOutput := engine.Match(matchInput)
|
||||||
|
|
||||||
|
// 3. Build response
|
||||||
|
resp := ParseNarrativeResponse{
|
||||||
|
Components: parseResult.Components,
|
||||||
|
EnergySources: parseResult.EnergySources,
|
||||||
|
LifecyclePhases: parseResult.LifecyclePhases,
|
||||||
|
Roles: parseResult.Roles,
|
||||||
|
Tags: parseResult.CustomTags,
|
||||||
|
TechSpecs: parseResult.TechSpecs,
|
||||||
|
Confidence: parseResult.Confidence,
|
||||||
|
MatchedPatterns: len(matchOutput.MatchedPatterns),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add suggested hazards from matched patterns
|
||||||
|
for _, mp := range matchOutput.MatchedPatterns {
|
||||||
|
for _, cat := range mp.GeneratedHazardCats {
|
||||||
|
resp.SuggestedHazards = append(resp.SuggestedHazards, struct {
|
||||||
|
Category string `json:"category"`
|
||||||
|
PatternID string `json:"pattern_id"`
|
||||||
|
PatternName string `json:"pattern_name"`
|
||||||
|
Priority int `json:"priority"`
|
||||||
|
}{
|
||||||
|
Category: cat,
|
||||||
|
PatternID: mp.ID,
|
||||||
|
PatternName: mp.NameDE,
|
||||||
|
Priority: mp.Priority,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, resp)
|
||||||
|
}
|
||||||
@@ -383,6 +383,7 @@ func registerIACERoutes(v1 *gin.RouterGroup, h *handlers.IACEHandler) {
|
|||||||
iaceRoutes.PUT("/projects/:id/hazards/:hid", h.UpdateHazard)
|
iaceRoutes.PUT("/projects/:id/hazards/:hid", h.UpdateHazard)
|
||||||
iaceRoutes.POST("/projects/:id/hazards/suggest", h.SuggestHazards)
|
iaceRoutes.POST("/projects/:id/hazards/suggest", h.SuggestHazards)
|
||||||
iaceRoutes.POST("/projects/:id/match-patterns", h.MatchPatterns)
|
iaceRoutes.POST("/projects/:id/match-patterns", h.MatchPatterns)
|
||||||
|
iaceRoutes.POST("/projects/:id/parse-narrative", h.ParseNarrative)
|
||||||
iaceRoutes.POST("/projects/:id/apply-patterns", h.ApplyPatternResults)
|
iaceRoutes.POST("/projects/:id/apply-patterns", h.ApplyPatternResults)
|
||||||
iaceRoutes.POST("/projects/:id/hazards/:hid/suggest-measures", h.SuggestMeasuresForHazard)
|
iaceRoutes.POST("/projects/:id/hazards/:hid/suggest-measures", h.SuggestMeasuresForHazard)
|
||||||
iaceRoutes.POST("/projects/:id/mitigations/:mid/suggest-evidence", h.SuggestEvidenceForMitigation)
|
iaceRoutes.POST("/projects/:id/mitigations/:mid/suggest-evidence", h.SuggestEvidenceForMitigation)
|
||||||
|
|||||||
@@ -171,6 +171,23 @@ func GetComponentLibrary() []ComponentLibraryEntry {
|
|||||||
{ID: "C118", NameDE: "VPN-Appliance", NameEN: "VPN Appliance", Category: "it_network", DescriptionDE: "VPN-Geraet fuer sichere Fernzugriffe auf die Maschinensteuerung.", TypicalHazardCategories: []string{"unauthorized_access"}, TypicalEnergySources: []string{}, MapsToComponentType: "network", Tags: []string{"networked", "it_component", "security_device"}, SortOrder: 118},
|
{ID: "C118", NameDE: "VPN-Appliance", NameEN: "VPN Appliance", Category: "it_network", DescriptionDE: "VPN-Geraet fuer sichere Fernzugriffe auf die Maschinensteuerung.", TypicalHazardCategories: []string{"unauthorized_access"}, TypicalEnergySources: []string{}, MapsToComponentType: "network", Tags: []string{"networked", "it_component", "security_device"}, SortOrder: 118},
|
||||||
{ID: "C119", NameDE: "KI-Inferenzmodul", NameEN: "AI Inference Module", Category: "it_network", DescriptionDE: "Dediziertes KI-Modul (GPU/TPU) fuer Echtzeit-Inferenz.", TypicalHazardCategories: []string{"false_classification", "model_drift", "unintended_bias"}, TypicalEnergySources: []string{}, MapsToComponentType: "network", Tags: []string{"has_ai", "has_software", "networked"}, SortOrder: 119},
|
{ID: "C119", NameDE: "KI-Inferenzmodul", NameEN: "AI Inference Module", Category: "it_network", DescriptionDE: "Dediziertes KI-Modul (GPU/TPU) fuer Echtzeit-Inferenz.", TypicalHazardCategories: []string{"false_classification", "model_drift", "unintended_bias"}, TypicalEnergySources: []string{}, MapsToComponentType: "network", Tags: []string{"has_ai", "has_software", "networked"}, SortOrder: 119},
|
||||||
{ID: "C120", NameDE: "Feldbus-Koppler", NameEN: "Fieldbus Coupler", Category: "it_network", DescriptionDE: "Koppler fuer PROFINET, EtherCAT oder andere Feldbussysteme.", TypicalHazardCategories: []string{"communication_failure"}, TypicalEnergySources: []string{}, MapsToComponentType: "network", Tags: []string{"networked", "it_component"}, SortOrder: 120},
|
{ID: "C120", NameDE: "Feldbus-Koppler", NameEN: "Fieldbus Coupler", Category: "it_network", DescriptionDE: "Koppler fuer PROFINET, EtherCAT oder andere Feldbussysteme.", TypicalHazardCategories: []string{"communication_failure"}, TypicalEnergySources: []string{}, MapsToComponentType: "network", Tags: []string{"networked", "it_component"}, SortOrder: 120},
|
||||||
|
|
||||||
|
// ── Extended: Press/Forming Machine Components (C121-C135) ───────────
|
||||||
|
{ID: "C121", NameDE: "Kniehebel-Mechanismus", NameEN: "Toggle Lever Mechanism", Category: "mechanical", DescriptionDE: "Kraft-Uebersetzungsmechanismus fuer Pressen (Kaltmassivumformung, Stanzen).", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN01", "EN02"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "high_force", "crush_point", "mechanical_transmission"}, SortOrder: 121},
|
||||||
|
{ID: "C122", NameDE: "Pressenstossel (hydraulisch)", NameEN: "Hydraulic Press Ram", Category: "mechanical", DescriptionDE: "Hydraulisch angetriebener Stossel fuer Umform- oder Stanzoperationen.", TypicalHazardCategories: []string{"mechanical_hazard", "pneumatic_hydraulic"}, TypicalEnergySources: []string{"EN01", "EN07"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "high_force", "crush_point", "high_speed", "gravity_risk"}, SortOrder: 122},
|
||||||
|
{ID: "C123", NameDE: "Schmieranlage (Zentralschmierung)", NameEN: "Central Lubrication System", Category: "hydraulic", DescriptionDE: "Automatische Zentralschmierung fuer Lager, Fuehrungen und Werkzeuge.", TypicalHazardCategories: []string{"pneumatic_hydraulic", "material_environmental"}, TypicalEnergySources: []string{"EN07"}, MapsToComponentType: "actuator", Tags: []string{"hydraulic_part", "oil_mist_risk"}, SortOrder: 123},
|
||||||
|
{ID: "C124", NameDE: "Absauganlage / Oelnebelabscheider", NameEN: "Extraction System / Oil Mist Separator", Category: "actuator", DescriptionDE: "Absaugung fuer Oelnebel, Staub, Daempfe im Pressenbereich.", TypicalHazardCategories: []string{"material_environmental", "noise_vibration"}, TypicalEnergySources: []string{"EN02"}, MapsToComponentType: "actuator", Tags: []string{"actuator_part", "noise_source"}, SortOrder: 124},
|
||||||
|
{ID: "C125", NameDE: "Ruettelplatte / Vibrationsfoerderer", NameEN: "Vibrating Plate / Feeder", Category: "mechanical", DescriptionDE: "Vibrationseinheit zum Sortieren, Ausrichten oder Foerdern von Teilen.", TypicalHazardCategories: []string{"noise_vibration", "ergonomic"}, TypicalEnergySources: []string{"EN01"}, MapsToComponentType: "mechanical", Tags: []string{"vibration_source", "noise_source", "moving_part"}, SortOrder: 125},
|
||||||
|
{ID: "C126", NameDE: "Stempel-Formen-System", NameEN: "Die/Punch Tooling System", Category: "mechanical", DescriptionDE: "Werkzeugset aus Stempel und Matrize fuer Umform- oder Stanzvorgaenge.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN01"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "high_force", "crush_point", "cutting_part"}, SortOrder: 126},
|
||||||
|
{ID: "C127", NameDE: "Transfersystem (Stangen/Greifer)", NameEN: "Transfer System (Bar/Gripper)", Category: "mechanical", DescriptionDE: "Mechanisches Transportsystem zwischen Bearbeitungsstationen.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN01"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "shear_risk", "pinch_point"}, SortOrder: 127},
|
||||||
|
{ID: "C128", NameDE: "Aufzugsportal / Hubwerk", NameEN: "Elevator Portal / Hoist", Category: "mechanical", DescriptionDE: "Hebevorrichtung fuer Materialzufuhr (Kisten, Paletten).", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN01", "EN04"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "gravity_risk", "high_force", "person_under_load"}, SortOrder: 128},
|
||||||
|
{ID: "C129", NameDE: "Fallrohr / Auswurfschacht", NameEN: "Chute / Ejection Channel", Category: "structural", DescriptionDE: "Schwerkraft-basierter Auswurf fuer fertige oder aussortierte Teile.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN04"}, MapsToComponentType: "mechanical", Tags: []string{"gravity_risk"}, SortOrder: 129},
|
||||||
|
{ID: "C130", NameDE: "Oelfangschale / Auffangwanne", NameEN: "Oil Drip Tray", Category: "structural", DescriptionDE: "Auffangvorrichtung fuer Hydraulikoel, Schmiermittel, Kuehlmittel.", TypicalHazardCategories: []string{"material_environmental"}, TypicalEnergySources: []string{}, MapsToComponentType: "mechanical", Tags: []string{"chemical_risk"}, SortOrder: 130},
|
||||||
|
{ID: "C131", NameDE: "Druckbegrenzungsventil", NameEN: "Pressure Relief Valve", Category: "hydraulic", DescriptionDE: "Sicherheitsventil zur Druckbegrenzung im Hydraulikkreis.", TypicalHazardCategories: []string{"pneumatic_hydraulic"}, TypicalEnergySources: []string{"EN07"}, MapsToComponentType: "actuator", Tags: []string{"hydraulic_part", "safety_device", "high_pressure"}, SortOrder: 131},
|
||||||
|
{ID: "C132", NameDE: "Werkzeugeinbauraum", NameEN: "Die Space / Tool Compartment", Category: "structural", DescriptionDE: "Bereich in der Presse in dem Werkzeuge eingebaut werden — Gefahrenzone.", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN01"}, MapsToComponentType: "mechanical", Tags: []string{"crush_point", "pinch_point"}, SortOrder: 132},
|
||||||
|
{ID: "C133", NameDE: "Schwungrad", NameEN: "Flywheel", Category: "mechanical", DescriptionDE: "Energiespeicher fuer mechanische Pressen (Drehenergie).", TypicalHazardCategories: []string{"mechanical_hazard"}, TypicalEnergySources: []string{"EN02", "EN03"}, MapsToComponentType: "mechanical", Tags: []string{"rotating_part", "stored_energy", "high_speed"}, SortOrder: 133},
|
||||||
|
{ID: "C134", NameDE: "Kistenwechselstation", NameEN: "Bin Changeover Station", Category: "mechanical", DescriptionDE: "Bereich zum manuellen Wechsel von Auffangkisten waehrend des Betriebs.", TypicalHazardCategories: []string{"mechanical_hazard", "ergonomic"}, TypicalEnergySources: []string{"EN04"}, MapsToComponentType: "mechanical", Tags: []string{"moving_part", "gravity_risk", "ergonomic"}, SortOrder: 134},
|
||||||
|
{ID: "C135", NameDE: "Waage / Pruefstation", NameEN: "Scale / Inspection Station", Category: "sensor", DescriptionDE: "Inline-Wiegestation oder Pruefeinrichtung zur Qualitaetskontrolle.", TypicalHazardCategories: []string{}, TypicalEnergySources: []string{}, MapsToComponentType: "sensor", Tags: []string{"sensor_part"}, SortOrder: 135},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -454,5 +454,136 @@ func GetBuiltinHazardPatterns() []HazardPattern {
|
|||||||
SuggestedEvidenceIDs: []string{"E01", "E15"},
|
SuggestedEvidenceIDs: []string{"E01", "E15"},
|
||||||
Priority: 80,
|
Priority: 80,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// Press/Forming Machine Patterns (HP045-HP058)
|
||||||
|
// ================================================================
|
||||||
|
{
|
||||||
|
ID: "HP045", NameDE: "Stoesselabsturz durch Druckverlust", NameEN: "Ram drop due to pressure loss",
|
||||||
|
RequiredComponentTags: []string{"hydraulic_part", "gravity_risk", "high_force"},
|
||||||
|
RequiredEnergyTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard", "pneumatic_hydraulic"},
|
||||||
|
SuggestedMeasureIDs: []string{"M001", "M051", "M054", "M131"},
|
||||||
|
SuggestedEvidenceIDs: []string{"E01", "E08", "E20"},
|
||||||
|
Priority: 98,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP046", NameDE: "Quetschen im Werkzeugeinbauraum", NameEN: "Crushing in die space",
|
||||||
|
RequiredComponentTags: []string{"crush_point", "high_force"},
|
||||||
|
RequiredEnergyTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M001", "M005", "M051", "M106"},
|
||||||
|
SuggestedEvidenceIDs: []string{"E01", "E08"},
|
||||||
|
Priority: 97,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP047", NameDE: "Oelnebelexposition Atemwege", NameEN: "Oil mist inhalation exposure",
|
||||||
|
RequiredComponentTags: []string{"hydraulic_part", "oil_mist_risk"},
|
||||||
|
RequiredEnergyTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"material_environmental"},
|
||||||
|
SuggestedMeasureIDs: []string{"M124", "M141"},
|
||||||
|
SuggestedEvidenceIDs: []string{"E20"},
|
||||||
|
Priority: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP048", NameDE: "Verbrennung durch heisse Werkstuecke", NameEN: "Burns from hot workpieces",
|
||||||
|
RequiredComponentTags: []string{"moving_part"},
|
||||||
|
RequiredEnergyTags: []string{"thermal"},
|
||||||
|
GeneratedHazardCats: []string{"thermal_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M054", "M141"},
|
||||||
|
SuggestedEvidenceIDs: []string{"E08"},
|
||||||
|
Priority: 85,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP049", NameDE: "Schwebende Last (Hubwerk/Aufzug)", NameEN: "Suspended load (hoist/elevator)",
|
||||||
|
RequiredComponentTags: []string{"gravity_risk", "person_under_load"},
|
||||||
|
RequiredEnergyTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M001", "M005", "M051"},
|
||||||
|
SuggestedEvidenceIDs: []string{"E01", "E08"},
|
||||||
|
Priority: 95,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP050", NameDE: "Einziehen/Scheren Transfersystem", NameEN: "Draw-in/shearing at transfer system",
|
||||||
|
RequiredComponentTags: []string{"moving_part", "shear_risk"},
|
||||||
|
RequiredEnergyTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M001", "M005", "M051"},
|
||||||
|
SuggestedEvidenceIDs: []string{"E01", "E08"},
|
||||||
|
Priority: 90,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP051", NameDE: "Sturzgefahr Auswurfbereich", NameEN: "Fall hazard at ejection area",
|
||||||
|
RequiredComponentTags: []string{"gravity_risk"},
|
||||||
|
RequiredEnergyTags: []string{},
|
||||||
|
ExcludedComponentTags: []string{"safety_device"},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M051", "M141"},
|
||||||
|
SuggestedEvidenceIDs: []string{"E20"},
|
||||||
|
Priority: 75,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP052", NameDE: "Druckfreisetzung Hydraulikspeicher", NameEN: "Pressure release from hydraulic accumulator",
|
||||||
|
RequiredComponentTags: []string{"hydraulic_part", "high_pressure"},
|
||||||
|
RequiredEnergyTags: []string{"stored_energy"},
|
||||||
|
GeneratedHazardCats: []string{"pneumatic_hydraulic"},
|
||||||
|
SuggestedMeasureIDs: []string{"M051", "M131"},
|
||||||
|
SuggestedEvidenceIDs: []string{"E01", "E08"},
|
||||||
|
Priority: 92,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP053", NameDE: "Impulslaerm Pressvorgang", NameEN: "Impact noise during press operation",
|
||||||
|
RequiredComponentTags: []string{"noise_source", "high_force"},
|
||||||
|
RequiredEnergyTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"noise_vibration"},
|
||||||
|
SuggestedMeasureIDs: []string{"M141"},
|
||||||
|
SuggestedEvidenceIDs: []string{"E20"},
|
||||||
|
Priority: 70,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP054", NameDE: "Schwungrad-Restenergie nach Abschaltung", NameEN: "Flywheel residual energy after shutdown",
|
||||||
|
RequiredComponentTags: []string{"rotating_part", "stored_energy"},
|
||||||
|
RequiredEnergyTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"mechanical_hazard"},
|
||||||
|
SuggestedMeasureIDs: []string{"M001", "M054"},
|
||||||
|
SuggestedEvidenceIDs: []string{"E01", "E08"},
|
||||||
|
Priority: 88,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP055", NameDE: "Umgehung Schutzeinrichtung (Pressentuer)", NameEN: "Bypass of safety guard (press door)",
|
||||||
|
RequiredComponentTags: []string{"interlocked", "crush_point"},
|
||||||
|
RequiredEnergyTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"safety_function_failure"},
|
||||||
|
SuggestedMeasureIDs: []string{"M005", "M106"},
|
||||||
|
SuggestedEvidenceIDs: []string{"E01", "E08"},
|
||||||
|
Priority: 96,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP056", NameDE: "Fehlbedienung Zweihandschaltung", NameEN: "Two-hand control misoperation",
|
||||||
|
RequiredComponentTags: []string{"two_hand_control_required"},
|
||||||
|
RequiredEnergyTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"safety_function_failure"},
|
||||||
|
SuggestedMeasureIDs: []string{"M106"},
|
||||||
|
SuggestedEvidenceIDs: []string{"E01"},
|
||||||
|
Priority: 90,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP057", NameDE: "Hydraulikoelleckage + Rutschgefahr", NameEN: "Hydraulic oil leakage + slip hazard",
|
||||||
|
RequiredComponentTags: []string{"hydraulic_part", "chemical_risk"},
|
||||||
|
RequiredEnergyTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"material_environmental"},
|
||||||
|
SuggestedMeasureIDs: []string{"M141"},
|
||||||
|
SuggestedEvidenceIDs: []string{"E20"},
|
||||||
|
Priority: 65,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "HP058", NameDE: "Ergonomische Belastung Kistenwechsel", NameEN: "Ergonomic strain during bin changeover",
|
||||||
|
RequiredComponentTags: []string{"ergonomic", "moving_part"},
|
||||||
|
RequiredEnergyTags: []string{},
|
||||||
|
GeneratedHazardCats: []string{"ergonomic"},
|
||||||
|
SuggestedMeasureIDs: []string{"M141"},
|
||||||
|
SuggestedEvidenceIDs: []string{"E20"},
|
||||||
|
Priority: 55,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package iace
|
||||||
|
|
||||||
|
// KeywordEntry maps German/English keywords to component library IDs,
|
||||||
|
// energy source IDs, and additional tags for deterministic text parsing.
|
||||||
|
type KeywordEntry struct {
|
||||||
|
Keywords []string // Search terms (lowercase, may include partial matches)
|
||||||
|
ComponentIDs []string // Matched component library IDs (C001-C135)
|
||||||
|
EnergyIDs []string // Matched energy source IDs (EN01-EN20)
|
||||||
|
ExtraTags []string // Additional tags derived from keyword context
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKeywordDictionary returns the complete keyword dictionary for
|
||||||
|
// deterministic narrative parsing. ~250 entries covering industrial
|
||||||
|
// 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"}},
|
||||||
|
|
||||||
|
// ── Foerdertechnik ──────────────────────────────────────────────
|
||||||
|
{Keywords: []string{"foerderband", "transportband", "conveyor"}, ComponentIDs: []string{"C003"}, EnergyIDs: []string{"EN01", "EN02"}, ExtraTags: []string{"entanglement_risk"}},
|
||||||
|
{Keywords: []string{"transfer", "transferanlage", "transfersystem"}, ComponentIDs: []string{"C127"}, ExtraTags: []string{"shear_risk", "pinch_point"}},
|
||||||
|
{Keywords: []string{"aufzug", "elevator", "lift"}, ComponentIDs: []string{"C014", "C128"}, EnergyIDs: []string{"EN04"}, ExtraTags: []string{"gravity_risk", "person_under_load"}},
|
||||||
|
{Keywords: []string{"hubwerk", "hoist", "hubgeraet"}, ComponentIDs: []string{"C128"}, EnergyIDs: []string{"EN04"}, ExtraTags: []string{"gravity_risk", "person_under_load"}},
|
||||||
|
{Keywords: []string{"ruettel", "vibration", "vibrationsfoerderer"}, ComponentIDs: []string{"C125"}, ExtraTags: []string{"vibration_source", "noise_source"}},
|
||||||
|
{Keywords: []string{"fallrohr", "auswurf", "chute"}, ComponentIDs: []string{"C129"}, EnergyIDs: []string{"EN04"}, ExtraTags: []string{"gravity_risk"}},
|
||||||
|
{Keywords: []string{"kistenwechsel", "bin change"}, ComponentIDs: []string{"C134"}, ExtraTags: []string{"ergonomic", "gravity_risk"}},
|
||||||
|
{Keywords: []string{"drehtisch", "rotary table"}, ComponentIDs: []string{"C004"}, EnergyIDs: []string{"EN02"}, ExtraTags: []string{"rotating_part"}},
|
||||||
|
{Keywords: []string{"linearachse", "linear axis"}, ComponentIDs: []string{"C005"}, EnergyIDs: []string{"EN01"}, ExtraTags: []string{"crush_point"}},
|
||||||
|
{Keywords: []string{"waage", "wiege", "scale", "pruefstation"}, ComponentIDs: []string{"C135"}, ExtraTags: []string{"sensor_part"}},
|
||||||
|
|
||||||
|
// ── Antriebe ────────────────────────────────────────────────────
|
||||||
|
{Keywords: []string{"motor", "elektromotor", "drehstrom"}, ComponentIDs: []string{"C031"}, EnergyIDs: []string{"EN02", "EN05"}, ExtraTags: []string{"rotating_part"}},
|
||||||
|
{Keywords: []string{"servomotor", "servo"}, ComponentIDs: []string{"C032"}, EnergyIDs: []string{"EN02"}, ExtraTags: []string{"rotating_part", "high_speed"}},
|
||||||
|
{Keywords: []string{"frequenzumrichter", "vfd", "inverter"}, ComponentIDs: []string{"C034"}, EnergyIDs: []string{"EN05"}, ExtraTags: []string{"electrical_part"}},
|
||||||
|
{Keywords: []string{"getriebe", "gearbox"}, ComponentIDs: []string{"C035"}, EnergyIDs: []string{"EN02"}, ExtraTags: []string{"rotating_part"}},
|
||||||
|
{Keywords: []string{"riemen", "belt drive"}, ComponentIDs: []string{"C040"}, EnergyIDs: []string{"EN02"}, ExtraTags: []string{"entanglement_risk"}},
|
||||||
|
|
||||||
|
// ── Hydraulik ───────────────────────────────────────────────────
|
||||||
|
{Keywords: []string{"hydraulik", "hydraulisch", "hydraulic"}, ComponentIDs: []string{"C041"}, EnergyIDs: []string{"EN07"}, ExtraTags: []string{"hydraulic_part", "high_pressure"}},
|
||||||
|
{Keywords: []string{"hydraulikpumpe", "hydraulic pump"}, ComponentIDs: []string{"C041"}, EnergyIDs: []string{"EN07"}, ExtraTags: []string{"hydraulic_part"}},
|
||||||
|
{Keywords: []string{"hydraulikzylinder", "hydraulic cylinder"}, ComponentIDs: []string{"C042"}, EnergyIDs: []string{"EN07"}, ExtraTags: []string{"hydraulic_part", "moving_part"}},
|
||||||
|
{Keywords: []string{"hydraulikventil", "hydraulic valve"}, ComponentIDs: []string{"C043"}, EnergyIDs: []string{"EN07"}, ExtraTags: []string{"hydraulic_part"}},
|
||||||
|
{Keywords: []string{"hydraulikspeicher", "accumulator"}, ComponentIDs: []string{"C044"}, EnergyIDs: []string{"EN07", "EN10"}, ExtraTags: []string{"stored_energy", "rapid_energy_release"}},
|
||||||
|
{Keywords: []string{"hydraulikschlauch", "hose"}, ComponentIDs: []string{"C045"}, ExtraTags: []string{"hydraulic_part"}},
|
||||||
|
{Keywords: []string{"druckbegrenzung", "pressure relief"}, ComponentIDs: []string{"C131"}, ExtraTags: []string{"safety_device"}},
|
||||||
|
{Keywords: []string{"schmier", "lubrication", "schmieranlage"}, ComponentIDs: []string{"C123"}, EnergyIDs: []string{"EN07"}, ExtraTags: []string{"oil_mist_risk"}},
|
||||||
|
{Keywords: []string{"oelfang", "auffangwanne", "drip tray"}, ComponentIDs: []string{"C130"}, ExtraTags: []string{"chemical_risk"}},
|
||||||
|
|
||||||
|
// ── Pneumatik ───────────────────────────────────────────────────
|
||||||
|
{Keywords: []string{"pneumatik", "pneumatisch", "pneumatic"}, ComponentIDs: []string{"C051"}, EnergyIDs: []string{"EN08"}, ExtraTags: []string{"pneumatic_part"}},
|
||||||
|
{Keywords: []string{"kompressor", "compressor", "druckluft"}, ComponentIDs: []string{"C052"}, EnergyIDs: []string{"EN08"}, ExtraTags: []string{"noise_source"}},
|
||||||
|
{Keywords: []string{"vakuum", "vacuum", "sauger"}, ComponentIDs: []string{"C055"}, EnergyIDs: []string{"EN08"}, ExtraTags: []string{}},
|
||||||
|
|
||||||
|
// ── Elektrik ────────────────────────────────────────────────────
|
||||||
|
{Keywords: []string{"schaltschrank", "cabinet", "electrical cabinet"}, ComponentIDs: []string{"C061"}, EnergyIDs: []string{"EN05"}, ExtraTags: []string{"high_voltage", "electrical_part"}},
|
||||||
|
{Keywords: []string{"stromversorgung", "power supply"}, ComponentIDs: []string{"C062"}, EnergyIDs: []string{"EN05"}, ExtraTags: []string{"electrical_part"}},
|
||||||
|
{Keywords: []string{"transformator", "transformer"}, ComponentIDs: []string{"C063"}, EnergyIDs: []string{"EN05"}, ExtraTags: []string{"high_voltage"}},
|
||||||
|
{Keywords: []string{"hauptschalter", "main switch"}, ComponentIDs: []string{"C070"}, EnergyIDs: []string{"EN05"}, ExtraTags: []string{"electrical_part"}},
|
||||||
|
{Keywords: []string{"fi-schutz", "rcd", "fehlerstrom"}, ComponentIDs: []string{"C066"}, ExtraTags: []string{"safety_device"}},
|
||||||
|
{Keywords: []string{"usv", "ups"}, ComponentIDs: []string{"C067"}, EnergyIDs: []string{"EN09"}, ExtraTags: []string{"stored_energy"}},
|
||||||
|
|
||||||
|
// ── Steuerung / SPS ─────────────────────────────────────────────
|
||||||
|
{Keywords: []string{"sps", "plc", "steuerung", "speicherprogrammierbar"}, ComponentIDs: []string{"C071"}, ExtraTags: []string{"has_software", "programmable"}},
|
||||||
|
{Keywords: []string{"sicherheits-sps", "safety plc", "f-sps"}, ComponentIDs: []string{"C072"}, ExtraTags: []string{"has_software", "safety_device"}},
|
||||||
|
{Keywords: []string{"hmi", "bedienfeld", "bedienpanel", "touchpanel"}, ComponentIDs: []string{"C073"}, ExtraTags: []string{"user_interface"}},
|
||||||
|
{Keywords: []string{"bedienpult", "control desk"}, ComponentIDs: []string{"C079"}, ExtraTags: []string{"user_interface"}},
|
||||||
|
|
||||||
|
// ── Sensorik ────────────────────────────────────────────────────
|
||||||
|
{Keywords: []string{"positionssensor", "position sensor", "initiator"}, ComponentIDs: []string{"C081"}, ExtraTags: []string{"sensor_part"}},
|
||||||
|
{Keywords: []string{"kamera", "vision", "bildverarbeitung"}, ComponentIDs: []string{"C082"}, ExtraTags: []string{"sensor_part"}},
|
||||||
|
{Keywords: []string{"kraftsensor", "force sensor", "kraftmessdose"}, ComponentIDs: []string{"C083"}, ExtraTags: []string{"sensor_part"}},
|
||||||
|
{Keywords: []string{"temperatursensor", "thermocouple"}, ComponentIDs: []string{"C084"}, ExtraTags: []string{"sensor_part"}},
|
||||||
|
{Keywords: []string{"drucksensor", "pressure sensor"}, ComponentIDs: []string{"C085"}, ExtraTags: []string{"sensor_part"}},
|
||||||
|
{Keywords: []string{"drehgeber", "encoder"}, ComponentIDs: []string{"C086"}, ExtraTags: []string{"sensor_part"}},
|
||||||
|
{Keywords: []string{"laserscanner", "laser scanner"}, ComponentIDs: []string{"C087"}, ExtraTags: []string{"sensor_part", "safety_device"}},
|
||||||
|
|
||||||
|
// ── Sicherheitseinrichtungen ────────────────────────────────────
|
||||||
|
{Keywords: []string{"not-halt", "not-aus", "emergency stop", "e-stop"}, ComponentIDs: []string{"C101"}, ExtraTags: []string{"safety_device", "emergency_stop"}},
|
||||||
|
{Keywords: []string{"lichtgitter", "lichtvorhang", "light curtain", "light grid"}, ComponentIDs: []string{"C102"}, ExtraTags: []string{"safety_device"}},
|
||||||
|
{Keywords: []string{"sicherheitsschalter", "safety switch"}, ComponentIDs: []string{"C104"}, ExtraTags: []string{"safety_device", "interlocked"}},
|
||||||
|
{Keywords: []string{"zuhaltung", "guard locking", "interlock"}, ComponentIDs: []string{"C105"}, ExtraTags: []string{"safety_device", "interlocked"}},
|
||||||
|
{Keywords: []string{"zweihand", "two-hand", "zweihandschaltung"}, ComponentIDs: []string{"C106"}, ExtraTags: []string{"safety_device", "two_hand_control_required"}},
|
||||||
|
{Keywords: []string{"schaltmatte", "safety mat"}, ComponentIDs: []string{"C108"}, ExtraTags: []string{"safety_device"}},
|
||||||
|
{Keywords: []string{"seilzug", "pull wire"}, ComponentIDs: []string{"C109"}, ExtraTags: []string{"safety_device"}},
|
||||||
|
{Keywords: []string{"zustimmtaster", "enabling device"}, ComponentIDs: []string{"C110"}, ExtraTags: []string{"safety_device"}},
|
||||||
|
|
||||||
|
// ── Schutzeinrichtungen / Strukturell ───────────────────────────
|
||||||
|
{Keywords: []string{"schutzumhausung", "schutzeinhausung", "enclosure"}, ComponentIDs: []string{"C022"}, ExtraTags: []string{"guard", "interlocked"}},
|
||||||
|
{Keywords: []string{"schutzgitter", "protective fence", "schutzzaun"}, ComponentIDs: []string{"C028"}, ExtraTags: []string{"guard"}},
|
||||||
|
{Keywords: []string{"schutztuer", "safety door", "schutztuer"}, ComponentIDs: []string{"C023"}, ExtraTags: []string{"guard", "interlocked"}},
|
||||||
|
|
||||||
|
// ── Absaugung / Umwelt ──────────────────────────────────────────
|
||||||
|
{Keywords: []string{"absaug", "extraction", "abscheider"}, ComponentIDs: []string{"C124"}, ExtraTags: []string{"noise_source"}},
|
||||||
|
{Keywords: []string{"filter", "filteranlage"}, ComponentIDs: []string{"C124"}, ExtraTags: []string{}},
|
||||||
|
|
||||||
|
// ── IT / Netzwerk ───────────────────────────────────────────────
|
||||||
|
{Keywords: []string{"switch", "netzwerk"}, ComponentIDs: []string{"C111"}, ExtraTags: []string{"networked"}},
|
||||||
|
{Keywords: []string{"firewall"}, ComponentIDs: []string{"C113"}, ExtraTags: []string{"networked"}},
|
||||||
|
{Keywords: []string{"iot", "gateway"}, ComponentIDs: []string{"C114"}, ExtraTags: []string{"networked"}},
|
||||||
|
{Keywords: []string{"wlan", "wifi", "wireless"}, ComponentIDs: []string{"C116"}, ExtraTags: []string{"wireless"}},
|
||||||
|
{Keywords: []string{"opc ua", "opc-ua"}, ComponentIDs: []string{"C117"}, ExtraTags: []string{"networked"}},
|
||||||
|
|
||||||
|
// ── KI / Software ───────────────────────────────────────────────
|
||||||
|
{Keywords: []string{"kuenstliche intelligenz", "ki-", "ai ", "machine learning", "neural"}, ComponentIDs: []string{"C119"}, ExtraTags: []string{"has_ai"}},
|
||||||
|
{Keywords: []string{"software", "firmware"}, ComponentIDs: []string{}, ExtraTags: []string{"has_software"}},
|
||||||
|
|
||||||
|
// ── Allgemeine Maschinen-Keywords ────────────────────────────────
|
||||||
|
{Keywords: []string{"roboter", "robot"}, ComponentIDs: []string{"C001"}, EnergyIDs: []string{"EN01", "EN02"}, ExtraTags: []string{"moving_part", "high_force"}},
|
||||||
|
{Keywords: []string{"greifer", "gripper"}, ComponentIDs: []string{"C002"}, ExtraTags: []string{"clamping_part", "pinch_point"}},
|
||||||
|
{Keywords: []string{"spindel", "spindle"}, ComponentIDs: []string{"C006"}, EnergyIDs: []string{"EN02"}, ExtraTags: []string{"rotating_part", "high_speed"}},
|
||||||
|
{Keywords: []string{"saege", "saw"}, ComponentIDs: []string{"C007"}, ExtraTags: []string{"cutting_part"}},
|
||||||
|
{Keywords: []string{"walze", "roller"}, ComponentIDs: []string{"C009"}, EnergyIDs: []string{"EN02"}, ExtraTags: []string{"rotating_part", "entanglement_risk"}},
|
||||||
|
{Keywords: []string{"kette", "chain"}, ComponentIDs: []string{"C010"}, ExtraTags: []string{"entanglement_risk"}},
|
||||||
|
{Keywords: []string{"bremse", "brake"}, ComponentIDs: []string{"C013"}, ExtraTags: []string{"safety_device"}},
|
||||||
|
{Keywords: []string{"schweiss", "weld"}, ComponentIDs: []string{"C016"}, EnergyIDs: []string{"EN05"}, ExtraTags: []string{"high_temperature"}},
|
||||||
|
{Keywords: []string{"biege", "bend"}, ComponentIDs: []string{"C019"}, ExtraTags: []string{"high_force"}},
|
||||||
|
{Keywords: []string{"stanz", "stamp", "punch"}, ComponentIDs: []string{"C018"}, ExtraTags: []string{"high_force", "crush_point"}},
|
||||||
|
{Keywords: []string{"heiz", "heater", "heating"}, ComponentIDs: []string{"C094"}, EnergyIDs: []string{"EN06"}, ExtraTags: []string{"high_temperature"}},
|
||||||
|
{Keywords: []string{"kuehl", "cool"}, ComponentIDs: []string{"C095"}, ExtraTags: []string{}},
|
||||||
|
{Keywords: []string{"luefter", "fan", "geblaese"}, ComponentIDs: []string{"C096"}, ExtraTags: []string{"rotating_part", "noise_source"}},
|
||||||
|
{Keywords: []string{"spannvorrichtung", "fixture", "clamp"}, ComponentIDs: []string{"C100"}, ExtraTags: []string{"clamping_part"}},
|
||||||
|
|
||||||
|
// ── Kontext-Keywords (keine Komponente, nur Tags) ───────────────
|
||||||
|
{Keywords: []string{"vollautomatisch", "automatisch"}, ExtraTags: []string{"auto_operation"}},
|
||||||
|
{Keywords: []string{"schutzausruestung", "psa", "ppe"}, ExtraTags: []string{"ppe_required"}},
|
||||||
|
{Keywords: []string{"gehoerschutz", "hearing protection"}, ExtraTags: []string{"noise_source", "ppe_required"}},
|
||||||
|
{Keywords: []string{"sicherheitsschuh", "safety shoe"}, ExtraTags: []string{"ppe_required"}},
|
||||||
|
{Keywords: []string{"fehlanwendung", "misuse"}, ExtraTags: []string{"bypass_risk"}},
|
||||||
|
{Keywords: []string{"umgehung", "bypass"}, ExtraTags: []string{"bypass_risk"}},
|
||||||
|
{Keywords: []string{"explosion", "explosionsgefaehrd"}, ExtraTags: []string{"chemical_risk"}},
|
||||||
|
{Keywords: []string{"flurfoerderfahrzeug", "forklift", "gabelstapler"}, ExtraTags: []string{"moving_part", "gravity_risk"}},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
package iace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ComponentMatch represents a component detected from narrative text.
|
||||||
|
type ComponentMatch struct {
|
||||||
|
LibraryID string `json:"library_id"`
|
||||||
|
NameDE string `json:"name_de"`
|
||||||
|
MatchedOn string `json:"matched_on"` // The keyword that triggered the match
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
Confidence float64 `json:"confidence"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnergyMatch represents an energy source detected from narrative text.
|
||||||
|
type EnergyMatch struct {
|
||||||
|
SourceID string `json:"source_id"`
|
||||||
|
NameDE string `json:"name_de"`
|
||||||
|
MatchedOn string `json:"matched_on"`
|
||||||
|
Value string `json:"value,omitempty"` // e.g., "20000 kN", "400 V"
|
||||||
|
Severity int `json:"severity"` // Derived severity 1-5
|
||||||
|
}
|
||||||
|
|
||||||
|
// TechSpec represents an extracted technical specification.
|
||||||
|
type TechSpec struct {
|
||||||
|
Value float64 `json:"value"`
|
||||||
|
Unit string `json:"unit"`
|
||||||
|
Raw string `json:"raw"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseResult contains all entities extracted from a machine narrative.
|
||||||
|
type ParseResult struct {
|
||||||
|
Components []ComponentMatch `json:"components"`
|
||||||
|
EnergySources []EnergyMatch `json:"energy_sources"`
|
||||||
|
LifecyclePhases []string `json:"lifecycle_phases"`
|
||||||
|
Roles []string `json:"roles"`
|
||||||
|
CustomTags []string `json:"custom_tags"`
|
||||||
|
TechSpecs []TechSpec `json:"tech_specs"`
|
||||||
|
Confidence float64 `json:"confidence"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// techSpecPattern matches numeric values with engineering units.
|
||||||
|
var techSpecPattern = regexp.MustCompile(`(\d[\d.,]*)\s*(kN|Tonnen|tonnen|kJ|kW|MW|V|kV|Hz|°C|bar|mm|m³/h|/min|U/min|rpm|m/s)`)
|
||||||
|
|
||||||
|
// lifecycleKeywords maps German text patterns to lifecycle phase IDs.
|
||||||
|
var lifecycleKeywords = map[string]string{
|
||||||
|
"betrieb": "normal_operation",
|
||||||
|
"normalbetrieb": "normal_operation",
|
||||||
|
"automatikbetrieb":"auto_operation",
|
||||||
|
"einricht": "setup",
|
||||||
|
"umruest": "changeover",
|
||||||
|
"wartung": "maintenance",
|
||||||
|
"instandhalt": "maintenance",
|
||||||
|
"instandsetz": "repair",
|
||||||
|
"reinig": "cleaning",
|
||||||
|
"transport": "transport",
|
||||||
|
"montage": "assembly",
|
||||||
|
"inbetriebnahme": "commissioning",
|
||||||
|
"ausserbetriebnahme": "decommissioning",
|
||||||
|
"demontage": "disposal",
|
||||||
|
"reparatur": "repair",
|
||||||
|
"stoerungsbeseitig":"fault_clearing",
|
||||||
|
}
|
||||||
|
|
||||||
|
// roleKeywords maps German text patterns to role IDs.
|
||||||
|
var roleKeywords = map[string]string{
|
||||||
|
"bedienpersonal": "operator",
|
||||||
|
"bediener": "operator",
|
||||||
|
"werker": "operator",
|
||||||
|
"einrichter": "setup_personnel",
|
||||||
|
"instandhalt": "maintenance_tech",
|
||||||
|
"wartungspersonal": "maintenance_tech",
|
||||||
|
"elektrofachkraft":"electrical_tech",
|
||||||
|
"besucher": "visitor",
|
||||||
|
"fremdfirma": "contractor",
|
||||||
|
"reinigungspersonal": "cleaning_staff",
|
||||||
|
"aufsichtsperson": "supervisor",
|
||||||
|
"programmierer": "programmer",
|
||||||
|
"auszubildend": "trainee",
|
||||||
|
"leiharbeiter": "temp_worker",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
result := ParseResult{}
|
||||||
|
if text == "" {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize text
|
||||||
|
lower := strings.ToLower(text)
|
||||||
|
lower = strings.ReplaceAll(lower, "ä", "ae")
|
||||||
|
lower = strings.ReplaceAll(lower, "ö", "oe")
|
||||||
|
lower = strings.ReplaceAll(lower, "ü", "ue")
|
||||||
|
lower = strings.ReplaceAll(lower, "ß", "ss")
|
||||||
|
|
||||||
|
// 1. Extract technical specifications
|
||||||
|
result.TechSpecs = extractTechSpecs(text)
|
||||||
|
|
||||||
|
// 2. Match keywords → components + energy + tags
|
||||||
|
dictionary := GetKeywordDictionary()
|
||||||
|
compLib := GetComponentLibrary()
|
||||||
|
compMap := make(map[string]ComponentLibraryEntry)
|
||||||
|
for _, c := range compLib {
|
||||||
|
compMap[c.ID] = c
|
||||||
|
}
|
||||||
|
|
||||||
|
seenComponents := make(map[string]bool)
|
||||||
|
seenEnergy := make(map[string]bool)
|
||||||
|
tagSet := make(map[string]bool)
|
||||||
|
|
||||||
|
for _, entry := range dictionary {
|
||||||
|
for _, kw := range entry.Keywords {
|
||||||
|
kwNorm := strings.ToLower(kw)
|
||||||
|
kwNorm = strings.ReplaceAll(kwNorm, "ä", "ae")
|
||||||
|
kwNorm = strings.ReplaceAll(kwNorm, "ö", "oe")
|
||||||
|
kwNorm = strings.ReplaceAll(kwNorm, "ü", "ue")
|
||||||
|
kwNorm = strings.ReplaceAll(kwNorm, "ß", "ss")
|
||||||
|
|
||||||
|
if strings.Contains(lower, kwNorm) {
|
||||||
|
// Add components
|
||||||
|
for _, cid := range entry.ComponentIDs {
|
||||||
|
if !seenComponents[cid] {
|
||||||
|
seenComponents[cid] = true
|
||||||
|
comp := compMap[cid]
|
||||||
|
result.Components = append(result.Components, ComponentMatch{
|
||||||
|
LibraryID: cid,
|
||||||
|
NameDE: comp.NameDE,
|
||||||
|
MatchedOn: kw,
|
||||||
|
Tags: comp.Tags,
|
||||||
|
Confidence: 0.8,
|
||||||
|
})
|
||||||
|
// Add component tags
|
||||||
|
for _, t := range comp.Tags {
|
||||||
|
tagSet[t] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add energy sources
|
||||||
|
for _, eid := range entry.EnergyIDs {
|
||||||
|
if !seenEnergy[eid] {
|
||||||
|
seenEnergy[eid] = true
|
||||||
|
result.EnergySources = append(result.EnergySources, EnergyMatch{
|
||||||
|
SourceID: eid,
|
||||||
|
NameDE: eid, // Will be enriched by caller
|
||||||
|
MatchedOn: kw,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add extra tags
|
||||||
|
for _, t := range entry.ExtraTags {
|
||||||
|
tagSet[t] = true
|
||||||
|
}
|
||||||
|
break // First keyword match is enough per entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Derive energy from tech specs
|
||||||
|
for _, spec := range result.TechSpecs {
|
||||||
|
deriveEnergyFromSpec(spec, &result, seenEnergy, tagSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Extract lifecycle phases
|
||||||
|
phaseSet := make(map[string]bool)
|
||||||
|
for kw, phase := range lifecycleKeywords {
|
||||||
|
kwNorm := strings.ReplaceAll(kw, "ä", "ae")
|
||||||
|
kwNorm = strings.ReplaceAll(kwNorm, "ö", "oe")
|
||||||
|
kwNorm = strings.ReplaceAll(kwNorm, "ü", "ue")
|
||||||
|
if strings.Contains(lower, kwNorm) {
|
||||||
|
if !phaseSet[phase] {
|
||||||
|
phaseSet[phase] = true
|
||||||
|
result.LifecyclePhases = append(result.LifecyclePhases, phase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Extract roles
|
||||||
|
roleSet := make(map[string]bool)
|
||||||
|
for kw, role := range roleKeywords {
|
||||||
|
if strings.Contains(lower, kw) {
|
||||||
|
if !roleSet[role] {
|
||||||
|
roleSet[role] = true
|
||||||
|
result.Roles = append(result.Roles, role)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Collect all tags
|
||||||
|
for t := range tagSet {
|
||||||
|
result.CustomTags = append(result.CustomTags, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Calculate overall confidence
|
||||||
|
if len(result.Components) > 0 {
|
||||||
|
result.Confidence = float64(len(result.Components)) / 15.0 // Normalize to ~1.0 for 15 components
|
||||||
|
if result.Confidence > 1.0 {
|
||||||
|
result.Confidence = 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractTechSpecs finds numeric values with engineering units in the text.
|
||||||
|
func extractTechSpecs(text string) []TechSpec {
|
||||||
|
matches := techSpecPattern.FindAllStringSubmatch(text, -1)
|
||||||
|
var specs []TechSpec
|
||||||
|
for _, m := range matches {
|
||||||
|
valStr := strings.ReplaceAll(m[1], ".", "")
|
||||||
|
valStr = strings.ReplaceAll(valStr, ",", ".")
|
||||||
|
val, err := strconv.ParseFloat(valStr, 64)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
specs = append(specs, TechSpec{
|
||||||
|
Value: val,
|
||||||
|
Unit: m[2],
|
||||||
|
Raw: m[0],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return specs
|
||||||
|
}
|
||||||
|
|
||||||
|
// deriveEnergyFromSpec maps technical values to energy sources and severity tags.
|
||||||
|
func deriveEnergyFromSpec(spec TechSpec, result *ParseResult, seen map[string]bool, tags map[string]bool) {
|
||||||
|
switch {
|
||||||
|
case (spec.Unit == "kN" || spec.Unit == "Tonnen" || spec.Unit == "tonnen") && spec.Value > 100:
|
||||||
|
addEnergy(result, seen, "EN01", spec.Raw)
|
||||||
|
tags["high_force"] = true
|
||||||
|
if spec.Value > 1000 {
|
||||||
|
tags["crush_point"] = true
|
||||||
|
}
|
||||||
|
case (spec.Unit == "V" || spec.Unit == "kV"):
|
||||||
|
if spec.Value >= 400 || spec.Unit == "kV" {
|
||||||
|
addEnergy(result, seen, "EN05", spec.Raw)
|
||||||
|
tags["high_voltage"] = true
|
||||||
|
} else if spec.Value >= 50 {
|
||||||
|
addEnergy(result, seen, "EN05", spec.Raw)
|
||||||
|
tags["electrical_part"] = true
|
||||||
|
}
|
||||||
|
case spec.Unit == "°C" && spec.Value > 60:
|
||||||
|
addEnergy(result, seen, "EN06", spec.Raw)
|
||||||
|
tags["high_temperature"] = true
|
||||||
|
if spec.Value > 100 {
|
||||||
|
tags["thermal_accumulation"] = true
|
||||||
|
}
|
||||||
|
case spec.Unit == "bar" && spec.Value > 10:
|
||||||
|
addEnergy(result, seen, "EN07", spec.Raw)
|
||||||
|
tags["high_pressure"] = true
|
||||||
|
case (spec.Unit == "kW" || spec.Unit == "MW") && spec.Value > 1:
|
||||||
|
addEnergy(result, seen, "EN02", spec.Raw)
|
||||||
|
tags["rotating_part"] = true
|
||||||
|
case (spec.Unit == "/min" || spec.Unit == "U/min" || spec.Unit == "rpm") && spec.Value > 100:
|
||||||
|
addEnergy(result, seen, "EN02", spec.Raw)
|
||||||
|
tags["rotating_part"] = true
|
||||||
|
if spec.Value > 500 {
|
||||||
|
tags["high_speed"] = true
|
||||||
|
}
|
||||||
|
case spec.Unit == "kJ" && spec.Value > 10:
|
||||||
|
addEnergy(result, seen, "EN03", spec.Raw)
|
||||||
|
tags["stored_energy"] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addEnergy(result *ParseResult, seen map[string]bool, id, matchedOn string) {
|
||||||
|
if !seen[id] {
|
||||||
|
seen[id] = true
|
||||||
|
result.EnergySources = append(result.EnergySources, EnergyMatch{
|
||||||
|
SourceID: id,
|
||||||
|
MatchedOn: matchedOn,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -86,6 +86,16 @@ func GetTagTaxonomy() []TagEntry {
|
|||||||
{ID: "communication_risk", Domain: "hazard", DescriptionDE: "Kommunikationsausfall-Risiko", DescriptionEN: "Communication failure risk"},
|
{ID: "communication_risk", Domain: "hazard", DescriptionDE: "Kommunikationsausfall-Risiko", DescriptionEN: "Communication failure risk"},
|
||||||
{ID: "emc_risk", Domain: "hazard", DescriptionDE: "EMV-Stoerungsrisiko", DescriptionEN: "EMC interference risk"},
|
{ID: "emc_risk", Domain: "hazard", DescriptionDE: "EMV-Stoerungsrisiko", DescriptionEN: "EMC interference risk"},
|
||||||
|
|
||||||
|
// ── Extended hazard/component tags (press/forming machines) ─────────
|
||||||
|
{ID: "person_under_load", Domain: "hazard", DescriptionDE: "Person unter schwebender Last", DescriptionEN: "Person under suspended load"},
|
||||||
|
{ID: "two_hand_control_required", Domain: "component", DescriptionDE: "Zweihandbedienung erforderlich", DescriptionEN: "Two-hand control required"},
|
||||||
|
{ID: "thermal_accumulation", Domain: "energy", DescriptionDE: "Waermeakkumulation ueber Zeit", DescriptionEN: "Thermal accumulation over time"},
|
||||||
|
{ID: "mechanical_transmission", Domain: "component", DescriptionDE: "Mechanische Kraftuebertragung", DescriptionEN: "Mechanical power transmission"},
|
||||||
|
{ID: "oil_mist_risk", Domain: "hazard", DescriptionDE: "Oelnebel-/Aerosol-Exposition", DescriptionEN: "Oil mist/aerosol exposure"},
|
||||||
|
{ID: "rapid_energy_release", Domain: "energy", DescriptionDE: "Schlagartige Energiefreisetzung", DescriptionEN: "Rapid energy release"},
|
||||||
|
{ID: "gravity_suspended_load", Domain: "hazard", DescriptionDE: "Schwebende Last unter Schwerkraft", DescriptionEN: "Suspended load under gravity"},
|
||||||
|
{ID: "bypass_risk", Domain: "hazard", DescriptionDE: "Risiko der Sicherheitsumgehung", DescriptionEN: "Safety bypass risk"},
|
||||||
|
|
||||||
// ── Domain: measure (~10 tags) ──────────────────────────────────────
|
// ── Domain: measure (~10 tags) ──────────────────────────────────────
|
||||||
{ID: "guard_measure", Domain: "measure", DescriptionDE: "Trennende Schutzeinrichtung", DescriptionEN: "Separating guard measure"},
|
{ID: "guard_measure", Domain: "measure", DescriptionDE: "Trennende Schutzeinrichtung", DescriptionEN: "Separating guard measure"},
|
||||||
{ID: "interlock_measure", Domain: "measure", DescriptionDE: "Verriegelungsmassnahme", DescriptionEN: "Interlock measure"},
|
{ID: "interlock_measure", Domain: "measure", DescriptionDE: "Verriegelungsmassnahme", DescriptionEN: "Interlock measure"},
|
||||||
|
|||||||
Reference in New Issue
Block a user