package missionclass import ( "sort" "strings" ) // pscMission maps a PSC code prefix to mission codes. Deliberately narrow: only codes with a defensible // mission signal are mapped, so a generic code produces no tag rather than a wrong one. Extend with // vetted entries only; a broad code stays unmapped on purpose. var pscMission = map[string][]string{ "AC": {"SIGINT", "EW"}, // R&D, National Defense (curated, conservative) } // naicsMission maps a full NAICS code to mission codes (curated, conservative). A broad NAICS that // carries no defensible mission signal is intentionally left empty so it produces no tag. var naicsMission = map[string][]string{ "541330": {}, // Engineering Services: too broad to assign a mission, intentionally empty } // ClassifyCodes maps a PSC code and a NAICS code to mission Hits through the curated crosswalk. The PSC // match is by prefix, the NAICS match is exact. Mechanism is psc or naics, evidence is the matched code. // An unmapped or broad code yields no Hit. Output is in Codes order, byte stable. func ClassifyCodes(psc, naics string) []Hit { found := map[string]Hit{} psc = strings.ToUpper(strings.TrimSpace(psc)) if psc != "" { prefixes := make([]string, 0, len(pscMission)) for prefix := range pscMission { prefixes = append(prefixes, prefix) } sort.Strings(prefixes) // deterministic iteration over the prefix set for _, prefix := range prefixes { if strings.HasPrefix(psc, prefix) { for _, c := range pscMission[prefix] { found[c] = Hit{Code: c, Mechanism: MechPSC, Evidence: psc} } } } } naics = strings.TrimSpace(naics) if codes, ok := naicsMission[naics]; ok { for _, c := range codes { found[c] = Hit{Code: c, Mechanism: MechNAICS, Evidence: naics} } } return ordered(found) } // Classify unions the keyword, alias, and code mechanisms and dedupes by code, keeping the FIRST // mechanism seen in priority order keyword, alias, code (so the most direct evidence is recorded). The // result is in Codes order, byte stable. func Classify(text, psc, naics string, idx AliasIndex) []Hit { merged := map[string]Hit{} for _, h := range ClassifyKeyword(text) { if _, ok := merged[h.Code]; !ok { merged[h.Code] = h } } for _, h := range ClassifyAlias(text, idx) { if _, ok := merged[h.Code]; !ok { merged[h.Code] = h } } for _, h := range ClassifyCodes(psc, naics) { if _, ok := merged[h.Code]; !ok { merged[h.Code] = h } } return ordered(merged) }