feat(iace): Sprint 3A — Operational State Graph + fix(ucca) flaky keyword sort
State Graph: - 9 Standard-Betriebszustaende (startup, homing, automatic_operation, manual_operation, teach_mode, maintenance, cleaning, emergency_stop, recovery_mode) - 20 State-Transitions als gerichteter Graph - OperationalStates + StateTransitions Felder in HazardPattern, MatchInput, PatternMatch - patternMatches() filtert Patterns nach Betriebszustand (nil = feuert immer) - Narrative-Parser extrahiert States aus Maschinenbeschreibung (22 Keywords + 4 Transition-Keywords) - 27 bestehende Patterns mit State-Einschraenkungen annotiert (10 operational, 15 maintenance, 2 cobot) - MatchReason um operational_state + state_transition Typen erweitert (Explainability) - 6 neue Tests: NilFiresAlways, MaintenanceFilter, StateTransition, MatchReasons, Count, TransitionValid UCCA fix: - Stabiler Tiebreaker (Pattern-ID aufsteigend) bei gleichem Keyword-Score in MatchByKeywords - Behebt flaky TestControlPatternIndex_MatchByKeywords (1/10 Failure-Rate durch Go map iteration order) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,13 +33,15 @@ type TechSpec struct {
|
||||
|
||||
// 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"`
|
||||
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"`
|
||||
OperationalStates []string `json:"operational_states,omitempty"`
|
||||
StateTransitions []string `json:"state_transitions,omitempty"`
|
||||
}
|
||||
|
||||
// techSpecPattern matches numeric values with engineering units.
|
||||
@@ -91,6 +93,40 @@ var roleKeywords = map[string]string{
|
||||
"leiharbeiter": "temp_worker",
|
||||
}
|
||||
|
||||
// operationalStateKeywords maps German text patterns to operational state IDs.
|
||||
var operationalStateKeywords = map[string]string{
|
||||
"hochfahren": "startup",
|
||||
"anlauf": "startup",
|
||||
"anfahren": "startup",
|
||||
"referenzfahrt": "homing",
|
||||
"referenzpunkt": "homing",
|
||||
"automatikbetrieb": "automatic_operation",
|
||||
"automatisch": "automatic_operation",
|
||||
"handbetrieb": "manual_operation",
|
||||
"manuell": "manual_operation",
|
||||
"tippbetrieb": "manual_operation",
|
||||
"teach": "teach_mode",
|
||||
"einrichtbetrieb": "teach_mode",
|
||||
"programmier": "teach_mode",
|
||||
"wartung": "maintenance",
|
||||
"instandhaltung": "maintenance",
|
||||
"reinigung": "cleaning",
|
||||
"not-halt": "emergency_stop",
|
||||
"nothalt": "emergency_stop",
|
||||
"notabschaltung": "emergency_stop",
|
||||
"wiederanlauf": "recovery_mode",
|
||||
"wiederinbetriebnahme":"recovery_mode",
|
||||
"quittier": "recovery_mode",
|
||||
}
|
||||
|
||||
// stateTransitionKeywords maps keyword combinations to state transitions.
|
||||
var stateTransitionKeywords = map[string]string{
|
||||
"unerwarteter wiederanlauf": "maintenance→automatic_operation",
|
||||
"wiederanlauf nach not": "emergency_stop→recovery_mode",
|
||||
"automatischer anlauf": "startup→automatic_operation",
|
||||
"betriebsartwechsel": "manual_operation→automatic_operation",
|
||||
}
|
||||
|
||||
// ParseNarrative extracts components, energy sources, lifecycle phases,
|
||||
// roles, and tags from a machine description text. Fully deterministic,
|
||||
// no LLM required.
|
||||
@@ -221,12 +257,37 @@ func ParseNarrative(text string, machineType ...string) ParseResult {
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Collect all tags
|
||||
// 6. Extract operational states
|
||||
stateSet := make(map[string]bool)
|
||||
for kw, state := range operationalStateKeywords {
|
||||
kwNorm := strings.ReplaceAll(kw, "ä", "ae")
|
||||
kwNorm = strings.ReplaceAll(kwNorm, "ö", "oe")
|
||||
kwNorm = strings.ReplaceAll(kwNorm, "ü", "ue")
|
||||
if strings.Contains(lower, kwNorm) {
|
||||
if !stateSet[state] {
|
||||
stateSet[state] = true
|
||||
result.OperationalStates = append(result.OperationalStates, state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Extract state transitions
|
||||
transSet := make(map[string]bool)
|
||||
for kw, trans := range stateTransitionKeywords {
|
||||
if strings.Contains(lower, kw) {
|
||||
if !transSet[trans] {
|
||||
transSet[trans] = true
|
||||
result.StateTransitions = append(result.StateTransitions, trans)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Collect all tags
|
||||
for t := range tagSet {
|
||||
result.CustomTags = append(result.CustomTags, t)
|
||||
}
|
||||
|
||||
// 7. Calculate overall confidence
|
||||
// 9. 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 {
|
||||
|
||||
Reference in New Issue
Block a user