code wiki / (root) / nx_career_parse.nx

nx_career_parse.nx source

↩ module page · 182 lines · 8304 B

1// nx_career_parse.nx -- POSTING / RESUME -> SKILLS (career ring rung 2; frontier momentum 91). Turns real job 2// text into the skill-csv the matcher consumes, so "real opportunities" runs on live postings, not hand-entry. 3// v1 = SOVEREIGN LEXICON EXTRACTION (honest: term extraction, not an LLM): the skills lexicon is DATA 4// (knowledge/career/skills.lex, one line "skill|alias|alias..."), matching is case-insensitive with WORD 5// BOUNDARIES (salesforce / salesmanship do NOT count as sales), level = mention count clamped 1..5 (v1 heuristic, 6// stated). JD-PARSE extracts a posting's requirements; RESUME-PARSE extracts a person's profile; end-to-end the 7// pair feeds MATCH-SCORE. Composes the nx_career pure core. Argless = selftest gate; CLI: extract <textfile>. 8// expect_exit: 0 license_tier: ORIGINAL 9import "nx_career.nx" 10const K_MAGIC_65536: i64 = 65536 11const K_MAGIC_2048: i64 = 2048 12const K_MAGIC_1048576: i64 = 1048576 13const K_MAGIC_4096: i64 = 4096 14 15func cp_read(path: *u8, buf: *u8, cap: i64) -> i64 { 16 let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 - 1 } 17 var tot: i64 = 0 18 while tot < cap { let r: i64 = sys_read(fd, (buf as i64 + tot) as *u8, cap - tot); if r <= 0 { break } tot = tot + r } 19 sys_close(fd); return tot 20} 21func cp_alpha(c: i64) -> i64 { 22 if c >= 97 { if c <= 122 { return 1 } } 23 if c >= 48 { if c <= 57 { return 1 } } 24 return 0 25} 26// lowercase copy of NUL-terminated src into dst; returns length 27func cp_lower(dst: *u8, src: *u8, cap: i64) -> i64 { 28 var i: i64 = 0 29 while src[i] != (0 as u8) { 30 if i >= cap - 1 { break } 31 var c: i64 = src[i] as i64 32 if c >= 65 { if c <= 90 { c = c + 32 } } 33 dst[i] = c as u8 34 i = i + 1 35 } 36 dst[i] = 0 as u8 37 return i 38} 39// boundary-aware count of term in t[0,n): char before + after must be non-alphanumeric 40func cp_count_term(t: *u8, n: i64, term: *u8) -> i64 { 41 let m: i64 = slen(term) 42 if m == 0 { return 0 } 43 var cnt: i64 = 0 44 var i: i64 = 0 45 while i + m <= n { 46 var hit: i64 = 1 47 var k: i64 = 0 48 while k < m { if t[i+k] != term[k] { hit = 0; k = m } else { k = k + 1 } } 49 if hit == 1 { 50 var lb: i64 = 1 51 if i > 0 { if cp_alpha(t[i-1] as i64) == 1 { lb = 0 } } 52 var rb: i64 = 1 53 if i + m < n { if cp_alpha(t[i+m] as i64) == 1 { rb = 0 } } 54 if lb == 1 { if rb == 1 { cnt = cnt + 1 } } 55 i = i + m 56 } else { i = i + 1 } 57 } 58 return cnt 59} 60// extract skills from text using the lexicon -> "name:lvl,name:lvl" csv in out; returns csv length 61func cp_extract(text: *u8, lex: *u8, lexn: i64, out: *u8, cap: i64) -> i64 { 62 let tl: i64 = slen(text) 63 let tb: *u8 = sys_mmap(tl + 4) 64 cp_lower(tb, text, tl + 2) 65 let termb: *u8 = sys_mmap(160) 66 let nameb: *u8 = sys_mmap(160) 67 var o: i64 = 0 68 var i: i64 = 0 69 while i < lexn { 70 var j: i64 = i 71 while j < lexn { if lex[j] == (10 as u8) { break } j = j + 1 } 72 if lex[i] != (35 as u8) { 73 if j > i { 74 var total: i64 = 0 75 var first: i64 = 1 76 var s: i64 = i 77 while s < j { 78 var e: i64 = s 79 while e < j { if lex[e] == (124 as u8) { break } e = e + 1 } 80 var t: i64 = 0 81 while s + t < e { if t < 158 { termb[t] = lex[s + t] } t = t + 1 } 82 if t > 158 { t = 158 } 83 // trim a trailing CR 84 if t > 0 { if termb[t-1] == (13 as u8) { t = t - 1 } } 85 termb[t] = 0 as u8 86 if t > 0 { 87 if first == 1 { 88 var q: i64 = 0 89 while q <= t { nameb[q] = termb[q]; q = q + 1 } 90 first = 0 91 } 92 total = total + cp_count_term(tb, tl, termb) 93 } 94 s = e + 1 95 } 96 if total > 0 { 97 var lvl: i64 = total 98 if lvl > 5 { lvl = 5 } 99 if o > 0 { if o < cap - 1 { out[o] = 44 as u8; o = o + 1 } } 100 var q2: i64 = 0 101 while nameb[q2] != (0 as u8) { if o < cap - 1 { out[o] = nameb[q2]; o = o + 1 } q2 = q2 + 1 } 102 if o < cap - 1 { out[o] = 58 as u8; o = o + 1 } 103 if o < cap - 1 { out[o] = (48 + lvl) as u8; o = o + 1 } 104 } 105 } 106 } 107 i = j + 1 108 } 109 out[o] = 0 as u8 110 return o 111} 112 113func cp_selftest() -> i64 { 114 p("=== NX-CAREER-PARSE SELFTEST (JD-PARSE + RESUME-PARSE, lexicon + word boundaries, end-to-end match) ===\n" as *u8) 115 var ok: i64 = 1 116 let lex: *u8 = sys_mmap(K_MAGIC_65536) 117 let lexn: i64 = cp_read("knowledge/career/skills.lex" as *u8, lex, K_MAGIC_65536) 118 if lexn <= 0 { p("NX-CAREER-PARSE lexicon knowledge/career/skills.lex MISSING -- fail loud\n" as *u8); return 1 } 119 120 let csv: *u8 = sys_mmap(K_MAGIC_2048) 121 // JD-PARSE: a posting with boundary traps (Salesforce/salesmanship must NOT count as sales) 122 let posting: *u8 = "Account Executive at Westfield. We need sales talent: own the sales pipeline and hit the sales quota. CRM required: keep crm hygiene and crm reporting daily. Salesforce experience welcome; salesmanship cliches not required." as *u8 123 cp_extract(posting, lex, lexn, csv, K_MAGIC_2048) 124 p(" JD-PARSE posting -> " as *u8); p(csv); p("\n" as *u8) 125 if seq(csv, "sales:3,crm:3" as *u8) != 1 { ok = 0 } 126 127 // RESUME-PARSE: Emma's resume text 128 let resume: *u8 = "Emma. sales rep of the year with sales awards; grew enterprise sales and channel sales. crm admin who built crm workflows and crm dashboards. writing daily: writing blogs, writing docs, writing scripts, and writing newsletters." as *u8 129 let rcsv: *u8 = sys_mmap(K_MAGIC_2048) 130 cp_extract(resume, lex, lexn, rcsv, K_MAGIC_2048) 131 p(" RESUME-PARSE resume -> " as *u8); p(rcsv); p("\n" as *u8) 132 if seq(rcsv, "sales:4,crm:3,writing:5" as *u8) != 1 { ok = 0 } 133 134 // alias: "selling" maps to sales 135 let al: *u8 = sys_mmap(256) 136 cp_extract("I am great at selling." as *u8, lex, lexn, al, 256) 137 p(" alias selling -> " as *u8); p(al); p("\n" as *u8) 138 if seq(al, "sales:1" as *u8) != 1 { ok = 0 } 139 140 // boundary negative: substrings alone extract NOTHING 141 let ng: *u8 = sys_mmap(256) 142 let ngn: i64 = cp_extract("salesforce salesmanship crmx" as *u8, lex, lexn, ng, 256) 143 p(" boundary-negative extracted_len=" as *u8); pn(ngn); p("\n" as *u8) 144 if ngn != 0 { ok = 0 } 145 146 // END-TO-END: parsed resume vs parsed posting -> MATCH-SCORE 147 let nb: *u8 = sys_mmap(64 * 16) 148 let nb2: *u8 = sys_mmap(64 * 16) 149 let hn: *i64 = sys_mmap(8 * 16) as *i64 150 let hl: *i64 = sys_mmap(8 * 16) as *i64 151 let rn: *i64 = sys_mmap(8 * 16) as *i64 152 let rl: *i64 = sys_mmap(8 * 16) as *i64 153 let hc: i64 = skills_parse(rcsv, hn, hl, 16, nb) 154 let rc: i64 = skills_parse(csv, rn, rl, 16, nb2) 155 let sc: i64 = match_score(hn, hl, hc, rn, rl, rc) 156 p(" end-to-end: parsed resume vs parsed posting MATCH-SCORE = " as *u8); pn(sc); p("/100\n" as *u8) 157 if sc != 100 { ok = 0 } 158 159 p("NX-CAREER-PARSE-SELFTEST lexicon_bytes=" as *u8); pn(lexn); p(" " as *u8) 160 if ok == 1 { p("verdict=GREEN\n" as *u8); return 0 } 161 p("verdict=RED\n" as *u8) 162 return 1 163} 164 165func main(argc: i64, argv: *i64) -> i64 { 166 if argc < 2 { return cp_selftest() } 167 if seq(argv[1] as *u8, "extract" as *u8) == 1 { 168 if argc < 3 { p("usage: extract <textfile>\n" as *u8); return 1 } 169 let lex: *u8 = sys_mmap(K_MAGIC_65536) 170 let lexn: i64 = cp_read("knowledge/career/skills.lex" as *u8, lex, K_MAGIC_65536) 171 if lexn <= 0 { p("lexicon missing -- fail loud\n" as *u8); return 1 } 172 let txt: *u8 = sys_mmap(K_MAGIC_1048576) 173 let tn: i64 = cp_read(argv[2] as *u8, txt, K_MAGIC_1048576) 174 if tn <= 0 { p("text file missing -- fail loud\n" as *u8); return 1 } 175 txt[tn] = 0 as u8 176 let csv: *u8 = sys_mmap(K_MAGIC_4096) 177 cp_extract(txt, lex, lexn, csv, K_MAGIC_4096) 178 p("EXTRACT " as *u8); p(csv); p("\n" as *u8) 179 return 0 180 } 181 return cp_selftest() 182}