+24
-73
@@ -8,96 +8,47 @@ import (
|
||||
"github.com/local/glpi-neural-brain/internal/model"
|
||||
)
|
||||
|
||||
const (
|
||||
UncategorizedFilter = "__uncategorized__"
|
||||
UnsourcedFilter = "__unsourced__"
|
||||
)
|
||||
|
||||
// NodeFilter limits knowledge-bearing nodes by category and logical source.
|
||||
// Categories and Sources are AND-combined; values inside each dimension use
|
||||
// OR semantics. Empty dimensions and "*" mean unrestricted. MatchNone is used
|
||||
// for an empty administrative/runtime intersection and deliberately matches no
|
||||
// node.
|
||||
// NodeFilter limits knowledge-bearing nodes by the exact value stored in the
|
||||
// KB document's source field. Values are compared case-sensitively after
|
||||
// trimming surrounding whitespace. An empty Sources list means unrestricted.
|
||||
// No category, origin, URI or taxonomy fallback participates in matching.
|
||||
type NodeFilter struct {
|
||||
Categories []string
|
||||
Sources []string
|
||||
MatchNone bool
|
||||
Sources []string
|
||||
}
|
||||
|
||||
func (f NodeFilter) Matches(node model.Node) bool {
|
||||
if f.MatchNone {
|
||||
return false
|
||||
}
|
||||
return matchesDimension(node.Categories, f.Categories, UncategorizedFilter) &&
|
||||
matchesDimension(nodeSources(node), f.Sources, UnsourcedFilter)
|
||||
}
|
||||
|
||||
func matchesDimension(values, filters []string, emptyToken string) bool {
|
||||
if len(filters) == 0 {
|
||||
if len(f.Sources) == 0 {
|
||||
return true
|
||||
}
|
||||
wanted := make(map[string]struct{}, len(filters))
|
||||
for _, filter := range filters {
|
||||
filter = strings.ToLower(strings.TrimSpace(filter))
|
||||
if filter != "" {
|
||||
wanted[filter] = struct{}{}
|
||||
source := strings.TrimSpace(NodeSource(node))
|
||||
for _, wanted := range f.Sources {
|
||||
wanted = strings.TrimSpace(wanted)
|
||||
if wanted == "" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(wanted) == 0 {
|
||||
return true
|
||||
}
|
||||
if _, ok := wanted["*"]; ok {
|
||||
return true
|
||||
}
|
||||
clean := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
value = strings.TrimSpace(value)
|
||||
if value != "" {
|
||||
clean = append(clean, value)
|
||||
}
|
||||
}
|
||||
if len(clean) == 0 {
|
||||
_, ok := wanted[emptyToken]
|
||||
return ok
|
||||
}
|
||||
for _, value := range clean {
|
||||
if _, ok := wanted[strings.ToLower(value)]; ok {
|
||||
if source == wanted {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NodeSource returns the logical source shown to users. Explicit source
|
||||
// metadata wins. Research nodes fall back to their host name; all other nodes
|
||||
// fall back to their technical origin.
|
||||
// NodeSource returns only the explicit source metadata copied from the KB
|
||||
// file's top-level source field (or assigned explicitly to generated research
|
||||
// evidence). Technical origins such as glpi-kb are deliberately ignored.
|
||||
func NodeSource(node model.Node) string {
|
||||
values := nodeSources(node)
|
||||
if len(values) == 0 {
|
||||
return ""
|
||||
}
|
||||
return values[0]
|
||||
return metadataText(node.Metadata, "source")
|
||||
}
|
||||
|
||||
func nodeSources(node model.Node) []string {
|
||||
if source := metadataText(node.Metadata, "source"); source != "" {
|
||||
return []string{source}
|
||||
// SourceFromURL derives the explicit source value used for learned web
|
||||
// evidence. It is kept separate from NodeSource so ordinary nodes never fall
|
||||
// back to their URI implicitly.
|
||||
func SourceFromURL(raw string) string {
|
||||
parsed, err := url.Parse(strings.TrimSpace(raw))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
if node.Kind == "source" && strings.TrimSpace(node.Label) != "" {
|
||||
return []string{strings.TrimSpace(node.Label)}
|
||||
}
|
||||
if node.Kind == "external" {
|
||||
for _, raw := range []string{node.URI, node.ExternalID} {
|
||||
parsed, err := url.Parse(strings.TrimSpace(raw))
|
||||
if err == nil && strings.TrimSpace(parsed.Hostname()) != "" {
|
||||
return []string{strings.ToLower(strings.TrimSpace(parsed.Hostname()))}
|
||||
}
|
||||
}
|
||||
}
|
||||
if strings.TrimSpace(node.Origin) != "" {
|
||||
return []string{strings.TrimSpace(node.Origin)}
|
||||
}
|
||||
return nil
|
||||
return strings.ToLower(strings.TrimSpace(parsed.Hostname()))
|
||||
}
|
||||
|
||||
func metadataText(metadata map[string]any, key string) string {
|
||||
|
||||
Reference in New Issue
Block a user