package extraction import ( "context" "regexp" "sort" "strconv" "strings" ) // deterministic is the offline rules plus gazetteer extractor. It is fully deterministic and needs no // model, key, or network, so it is the production default for the closed vocabulary fields and is the // only extractor in this build. Every value it emits is a self reported mention, never a verified fact. type deterministic struct{} // NewDeterministic builds the offline extractor. func NewDeterministic() Extractor { return deterministic{} } // ExtractResume parses a resume into proposed candidate fields. func (deterministic) ExtractResume(_ context.Context, text string) (RawExtraction, error) { lower := strings.ToLower(text) level := detectClearanceLevel(lower) rx := RawExtraction{ FullName: detectName(text), ClearanceLevel: level, ClearancePoly: detectPoly(lower), ClearanceActive: level != "" && !reNegativeClearance.MatchString(lower), Skills: detectFromGazetteer(lower, skillMatchers), Certifications: detectFromGazetteer(lower, certMatchers), Agencies: detectFromGazetteer(lower, agencyMatchers), YearsExperience: detectYears(lower), Location: detectLocation(text), } return rx, nil } // ExtractRequirement parses a pasted job description into proposed requirement fields. The clearance is // the requirement's STATED required clearance (active is not meaningful for a requirement), and the // skills, certifications, and agency become the required set the candidate is scored against. func (deterministic) ExtractRequirement(_ context.Context, text string) (RawExtraction, error) { lower := strings.ToLower(text) rx := RawExtraction{ ClearanceLevel: detectClearanceLevel(lower), ClearancePoly: detectPoly(lower), Skills: detectFromGazetteer(lower, skillMatchers), Certifications: detectFromGazetteer(lower, certMatchers), Agencies: detectFromGazetteer(lower, agencyMatchers), YearsExperience: detectYears(lower), Location: detectLocation(text), } return rx, nil } // ---------- clearance ---------- var ( // TS/SCI in its common written forms. "sci" alone, or any of the ts sci spellings, implies TS/SCI. reTSSCI = regexp.MustCompile(`(?i)\b(ts\s*/?\s*sci|tssci|sci)\b`) reTopSecret = regexp.MustCompile(`(?i)\btop\s+secret\b`) reSecret = regexp.MustCompile(`(?i)\bsecret\b`) reConfid = regexp.MustCompile(`(?i)\bconfidential\b`) reFullScope = regexp.MustCompile(`(?i)\b(full[\s-]*scope|fs\s*poly|lifestyle\s*poly)\b`) reCIPoly = regexp.MustCompile(`(?i)\b(ci\s*poly|counter\s*intelligence\s*poly|counterintelligence)\b`) reNegativeCle = regexp.MustCompile(`(?i)\b(expired|inactive|lapsed|former\s+clearance|no\s+longer)\b`) reYears = regexp.MustCompile(`(?i)(\d{1,2})\s*\+?\s*years?`) reCityState = regexp.MustCompile(`\b([A-Z][a-zA-Z]+(?: [A-Z][a-zA-Z]+){0,2}),\s*([A-Z]{2})\b`) ) // reNegativeClearance is aliased so ExtractResume reads clearly. var reNegativeClearance = reNegativeCle // detectClearanceLevel returns the highest specificity clearance code found, empty when none. Order is // load bearing: top secret is checked before secret (secret is a substring of top secret), and TS/SCI // before top secret, so a resume that says "TS/SCI" is never downgraded to Secret. func detectClearanceLevel(lower string) string { switch { case reTSSCI.MatchString(lower) && (reTopSecret.MatchString(lower) || strings.Contains(lower, "sci")): return "ts_sci" case reTopSecret.MatchString(lower): return "top_secret" case reSecret.MatchString(lower): return "secret" case reConfid.MatchString(lower): return "confidential" default: return "" } } func detectPoly(lower string) string { switch { case reFullScope.MatchString(lower): return "full_scope" case reCIPoly.MatchString(lower): return "ci" default: return "" } } func detectYears(lower string) int { best := 0 for _, m := range reYears.FindAllStringSubmatch(lower, -1) { n, err := strconv.Atoi(m[1]) if err != nil { continue } if n > 50 { // implausible for a single career; treat as noise continue } if n > best { best = n } } return best } // detectLocation returns the first "City, ST" mention whose two letter code is a real USPS state or // territory, from the original (cased) text. Validating the code rejects job title and org fragments // like "Manager, HR" or "Director, VP" that share the City, ST shape, while keeping HI, NM, VA, DC. func detectLocation(text string) string { for _, m := range reCityState.FindAllStringSubmatch(text, -1) { if usStates[m[2]] { return strings.TrimSpace(m[1]) + ", " + m[2] } } return "" } // usStates is the set of USPS state, district, and territory codes used to validate a detected location. var usStates = map[string]bool{ "AL": true, "AK": true, "AZ": true, "AR": true, "CA": true, "CO": true, "CT": true, "DE": true, "FL": true, "GA": true, "HI": true, "ID": true, "IL": true, "IN": true, "IA": true, "KS": true, "KY": true, "LA": true, "ME": true, "MD": true, "MA": true, "MI": true, "MN": true, "MS": true, "MO": true, "MT": true, "NE": true, "NV": true, "NH": true, "NJ": true, "NM": true, "NY": true, "NC": true, "ND": true, "OH": true, "OK": true, "OR": true, "PA": true, "RI": true, "SC": true, "SD": true, "TN": true, "TX": true, "UT": true, "VT": true, "VA": true, "WA": true, "WV": true, "WI": true, "WY": true, "DC": true, "PR": true, "GU": true, "VI": true, "AS": true, "MP": true, } // ---------- name (resume only, best effort, used solely for the Save Candidate prefill) ---------- var headerWords = map[string]bool{ "resume": true, "curriculum": true, "vitae": true, "profile": true, "summary": true, "objective": true, "experience": true, "education": true, "skills": true, "contact": true, } // detectName returns the first line that looks like a person name: two to four capitalized alphabetic // tokens, no digits, not a section header. Empty when nothing qualifies (the form then asks for it). func detectName(text string) string { for _, line := range strings.Split(text, "\n") { line = strings.TrimSpace(line) if line == "" || strings.ContainsAny(line, "0123456789@") { continue } fields := strings.Fields(line) if len(fields) < 2 || len(fields) > 4 { continue } ok := true for _, f := range fields { if headerWords[strings.ToLower(strings.Trim(f, ".,"))] { ok = false break } r := []rune(f) if len(r) == 0 || r[0] < 'A' || r[0] > 'Z' { ok = false break } for _, c := range r { if !(c >= 'A' && c <= 'Z') && !(c >= 'a' && c <= 'z') && c != '.' && c != '-' && c != '\'' { ok = false break } } if !ok { break } } if ok { return line } } return "" } // ---------- gazetteers (canonical label plus the lowercased mentions that resolve to it) ---------- type gazEntry struct { canonical string mentions []string } // mentionMatcher matches one gazetteer mention. A wordlike mention (letters, digits, spaces) is matched // on a word boundary so a short token like "java" or "ato" matches "Java," or "ATO" but NOT the inside // of "javascript" or "potato". A mention carrying a regex special character (for example "c++", // "security+") falls back to a plain substring match, which is safe because those are distinctive. type mentionMatcher struct { re *regexp.Regexp // nil means use the substring path contains string } func (m mentionMatcher) match(lower string) bool { if m.re != nil { return m.re.MatchString(lower) } return strings.Contains(lower, m.contains) } // compiledGaz is a gazetteer entry with its mentions compiled to matchers, built once in init. type compiledGaz struct { canonical string matchers []mentionMatcher } var reWordlike = regexp.MustCompile(`^[a-z0-9][a-z0-9 ]*$`) func compileMentions(mentions []string) []mentionMatcher { out := make([]mentionMatcher, 0, len(mentions)) for _, m := range mentions { m = strings.TrimSpace(m) if m == "" { continue } if reWordlike.MatchString(m) { out = append(out, mentionMatcher{re: regexp.MustCompile(`(?i)\b` + regexp.QuoteMeta(m) + `\b`)}) } else { out = append(out, mentionMatcher{contains: m}) } } return out } func compileGaz(g []gazEntry) []compiledGaz { out := make([]compiledGaz, len(g)) for i, e := range g { out[i] = compiledGaz{canonical: e.canonical, matchers: compileMentions(e.mentions)} } return out } var ( skillMatchers []compiledGaz certMatchers []compiledGaz agencyMatchers []compiledGaz ) func init() { skillMatchers = compileGaz(skillGazetteer) certMatchers = compileGaz(certGazetteer) agencyMatchers = compileGaz(agencyGazetteer) } // detectFromGazetteer returns the canonical labels whose mentions appear in the lowercased text, // deduped and sorted so the output is deterministic regardless of order of appearance. func detectFromGazetteer(lower string, g []compiledGaz) []string { var out []string seen := map[string]bool{} for _, e := range g { for _, m := range e.matchers { if m.match(lower) { if !seen[e.canonical] { seen[e.canonical] = true out = append(out, e.canonical) } break } } } sort.Strings(out) return out } // skillGazetteer maps common cleared IT and cyber skills to their mentions. Canonical labels are chosen // to match the requirement skill ontology by name (the engine matches skills case insensitively by // name), so an extracted skill scores against a requirement that lists the same skill. var skillGazetteer = []gazEntry{ {"Python", []string{"python"}}, {"Java", []string{"java"}}, {"JavaScript", []string{"javascript", "typescript"}}, {"Go", []string{"golang"}}, {"C++", []string{"c++"}}, {"SQL", []string{"sql", "postgresql", "postgres", "mysql"}}, {"Linux", []string{"linux", "rhel", "red hat enterprise"}}, {"AWS", []string{"aws", "amazon web services"}}, {"Azure", []string{"azure"}}, {"Kubernetes", []string{"kubernetes", "k8s"}}, {"Docker", []string{"docker", "containerization"}}, {"Terraform", []string{"terraform"}}, {"Ansible", []string{"ansible"}}, {"Splunk", []string{"splunk"}}, {"SIEM", []string{"siem", "security information and event"}}, {"Incident Response", []string{"incident response", "incident handling"}}, {"Penetration Testing", []string{"penetration testing", "pen testing", "pentest"}}, {"Vulnerability Management", []string{"vulnerability management", "vulnerability assessment"}}, {"Network Security", []string{"network security", "firewall", "intrusion detection"}}, {"Cloud Security", []string{"cloud security"}}, {"DevSecOps", []string{"devsecops", "devops"}}, {"RMF", []string{"rmf", "risk management framework"}}, {"ATO", []string{"authority to operate", "ato"}}, {"NIST", []string{"nist 800", "nist sp 800"}}, {"Threat Intelligence", []string{"threat intelligence", "threat intel"}}, {"Active Directory", []string{"active directory"}}, {"VMware", []string{"vmware"}}, {"Machine Learning", []string{"machine learning", "deep learning"}}, {"Data Analysis", []string{"data analysis", "data analytics"}}, {"Cryptography", []string{"cryptography", "encryption"}}, {"Cybersecurity", []string{"cybersecurity", "cyber security"}}, {"Systems Engineering", []string{"systems engineering", "system engineering"}}, {"Software Engineering", []string{"software engineering", "software development"}}, {"Intelligence Analysis", []string{"intelligence analysis", "all source", "sigint", "geoint"}}, } // certGazetteer maps common GovCon IT and cyber certifications to their mentions. var certGazetteer = []gazEntry{ {"CISSP", []string{"cissp"}}, {"CISM", []string{"cism"}}, {"CISA", []string{"cisa"}}, {"CompTIA Security+", []string{"security+", "security plus", "comptia security"}}, {"CompTIA Network+", []string{"network+", "network plus"}}, {"CASP+", []string{"casp+", "comptia advanced security"}}, {"CEH", []string{"certified ethical hacker", "ceh certification"}}, {"CCNA", []string{"ccna"}}, {"CCNP", []string{"ccnp"}}, {"PMP", []string{"pmp", "project management professional"}}, {"ITIL", []string{"itil"}}, {"GSEC", []string{"gsec"}}, {"GCIH", []string{"gcih"}}, {"OSCP", []string{"oscp", "offensive security certified"}}, {"AWS Certified Solutions Architect", []string{"aws certified solutions architect", "aws solutions architect"}}, } // agencyGazetteer maps federal customers to their mentions, weighted toward the cleared defense space. var agencyGazetteer = []gazEntry{ {"Air Force", []string{"air force", "usaf"}}, {"Space Force", []string{"space force", "ussf"}}, {"Army", []string{"u.s. army", "united states army", "department of the army"}}, {"Navy", []string{"u.s. navy", "united states navy", "department of the navy"}}, {"Marine Corps", []string{"marine corps", "usmc"}}, {"DoD", []string{"department of defense", "dod", "d.o.d"}}, {"DISA", []string{"disa", "defense information systems agency"}}, {"NSA", []string{"national security agency", "nsa"}}, {"CIA", []string{"central intelligence agency"}}, {"DIA", []string{"defense intelligence agency"}}, {"NGA", []string{"national geospatial"}}, {"DHS", []string{"homeland security", "dhs"}}, {"FBI", []string{"federal bureau of investigation", "fbi"}}, {"DARPA", []string{"darpa"}}, {"CENTCOM", []string{"centcom", "central command"}}, {"SOCOM", []string{"socom", "special operations command"}}, {"INDOPACOM", []string{"indopacom", "pacom", "indo-pacific command"}}, }