005a2ed711
- Domain-gate ~15 foreign machine classes (pool, amusement, paint booth, tank farm, reactor, lathe/chips, saw, film/carton, robot, mobile cab, asbestos, playground swing) in pattern_domain_gates.go so ungated hazard patterns stop leaking into unrelated machines; matching emit keywords added in keyword_dictionary.go (gate+emit share one vocabulary). - Extend the cross-domain precision guard to 6 machine classes (press, cobot, motor, welding + the 2 GTs) with per-case homeDomains, so a machine's own domain terms are never flagged. GT coverage stays 100%. - Reconcile the fine-grained norm machine-type vocabulary (455 keys) with the 68 canonical dropdown keys via canonicalMachineType() family folding in matchNorm — welding 0->17, robotics_cobot 0->6, press 8->13, circular_saw 1->35 machine-specific C-norms. Pattern gating left strict. - Fix initialize?force=true summary index-shift that mislabeled counts (reported matched-patterns as "hazards"); now uses named step vars. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
81 lines
2.7 KiB
Go
81 lines
2.7 KiB
Go
package iace
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// The cross-domain term list lives in gt_benchmark_harness_test.go
|
|
// (foreignDomainTerms, term → home domain). This precision guard reuses it and,
|
|
// unlike the diagnostic TestGT_DomainLeakage, runs the FULL production gating
|
|
// path including the relevance backstop, then ASSERTS zero leaks. It catches
|
|
// machine-type wiring regressions and weak-tag (structural_part) leaks in CI.
|
|
|
|
// firedHazardsForCase runs the exact production gating path (parse → engine match
|
|
// → relevance backstop) for one GT case and returns the surviving patterns.
|
|
func firedHazardsForCase(c gtCase) []PatternMatch {
|
|
narrative := c.narrativeOverride
|
|
pr := ParseNarrative(narrative, c.machineType)
|
|
input := parseResultToMatchInput(pr, c.machineType)
|
|
|
|
compNames := make([]string, 0, len(pr.Components))
|
|
for _, comp := range pr.Components {
|
|
if comp.Negated {
|
|
continue
|
|
}
|
|
compNames = append(compNames, NormalizeDEPublic(comp.NameDE))
|
|
}
|
|
|
|
out := NewPatternEngine().Match(input)
|
|
fired := make([]PatternMatch, 0, len(out.MatchedPatterns))
|
|
for _, mp := range out.MatchedPatterns {
|
|
if IsPatternRelevant(mp, narrative, compNames) {
|
|
fired = append(fired, mp)
|
|
}
|
|
}
|
|
return fired
|
|
}
|
|
|
|
// TestCrossDomainPrecision asserts that no fired pattern is foreign to the
|
|
// machine — neither machine-type-incompatible nor matching a foreign-domain
|
|
// term. Runs over the 2 ground truths AND the 4 Grenzen-only machines
|
|
// (press/cobot/motor/welding), so the gating guard is validated across six
|
|
// machine classes. A term whose domain is in the case's homeDomains is its
|
|
// OWN domain and never counts as a leak.
|
|
func TestCrossDomainPrecision(t *testing.T) {
|
|
cases := append(append([]gtCase{}, gtBenchmarkCases...), precisionOnlyCases...)
|
|
for _, c := range cases {
|
|
c := c
|
|
home := make(map[string]bool, len(c.homeDomains))
|
|
for _, d := range c.homeDomains {
|
|
home[d] = true
|
|
}
|
|
t.Run(c.name, func(t *testing.T) {
|
|
fired := firedHazardsForCase(c)
|
|
t.Logf("%s (%s): %d patterns fired", c.name, c.machineType, len(fired))
|
|
|
|
var domainLeaks []string
|
|
for _, mp := range fired {
|
|
text := normalizeDE(mp.PatternName + " " + mp.ZoneDE + " " + mp.ScenarioDE)
|
|
for term, domain := range foreignDomainTerms {
|
|
if home[domain] {
|
|
continue // this domain is native to the machine
|
|
}
|
|
if strings.Contains(text, term) {
|
|
domainLeaks = append(domainLeaks, domain+"/"+term+" → "+mp.PatternName)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
sort.Strings(domainLeaks)
|
|
for _, l := range domainLeaks {
|
|
t.Logf(" FOREIGN-DOMAIN: %s", l)
|
|
}
|
|
if len(domainLeaks) > 0 {
|
|
t.Errorf("%s: %d cross-domain leak(s) — patterns from foreign machine classes fired", c.name, len(domainLeaks))
|
|
}
|
|
})
|
|
}
|
|
}
|