package iace import ( "sort" "strings" "testing" ) // producibleTagUniverse returns every component/energy tag that some machine // could ever carry: tags from the component library, the energy library, the // keyword dictionary's ExtraTags, and the domain-gate tags. A pattern whose // RequiredComponentTags/RequiredEnergyTags include a tag outside this set can // never match — no machine can produce that tag. func producibleTagUniverse() map[string]bool { u := make(map[string]bool) for _, c := range GetComponentLibrary() { for _, t := range c.Tags { u[t] = true } } for _, e := range GetEnergySources() { for _, t := range e.Tags { u[t] = true } } for _, k := range GetKeywordDictionary() { for _, t := range k.ExtraTags { u[t] = true } } // Domain-gate tags are produced from narrative domain terms. for _, t := range []string{ "dom_agri", "dom_cnc", "dom_escalator", "dom_glass", "dom_grinding", "dom_plastics", "dom_press", "dom_rolling", "dom_solar", "dom_textile", "dom_welding", "dom_wind", } { u[t] = true } return u } // TestPatternReachability reports patterns that can never fire because they // require a component/energy tag that nothing in the libraries produces. Every // pattern should be usable in SOME CE risk assessment. Currently informational // (t.Log) so we can review the list before deciding to prune. func TestPatternReachability(t *testing.T) { universe := producibleTagUniverse() patterns := AllPatterns() type dead struct { id, name string missing []string } var deads []dead missingTagCount := make(map[string]int) for _, p := range patterns { var missing []string for _, tag := range append(append([]string{}, p.RequiredComponentTags...), p.RequiredEnergyTags...) { if !universe[tag] { missing = append(missing, tag) missingTagCount[tag]++ } } if len(missing) > 0 { deads = append(deads, dead{p.ID, p.NameDE, missing}) } } t.Logf("Patterns gesamt: %d", len(patterns)) t.Logf("Unerreichbare (tote) Patterns: %d", len(deads)) // Most common unsatisfiable tags first — these point at the systemic gaps. type kv struct { tag string n int } var ranked []kv for tag, n := range missingTagCount { ranked = append(ranked, kv{tag, n}) } sort.Slice(ranked, func(i, j int) bool { return ranked[i].n > ranked[j].n }) t.Log("--- Unerfuellbare Required-Tags (Haeufigkeit) ---") for _, r := range ranked { t.Logf(" %3d %s", r.n, r.tag) } t.Log("--- Tote Patterns (erste 60) ---") for i, d := range deads { if i >= 60 { break } t.Logf(" %s %q fehlend: %s", d.id, d.name, strings.Join(d.missing, ",")) } // Guard: every pattern must be reachable by some CE risk assessment. A // pattern requiring a tag no component/energy/keyword can ever produce is // dead weight (and often a tag-naming typo). Keep this at zero. if len(deads) > 0 { t.Errorf("%d unreachable pattern(s) — required tags that nothing produces: %v", len(deads), missingTagCount) } }