nx_career.nx source
↩ module page · 217 lines · 10100 B
1// nx_career.nx -- CAREER / OPPORTUNITIES (the jobs ring of the Nishi Relationship OS). Operator 2026-07-09:
2// "help people find jobs with their recruiters ... get real opportunities at where they are and where they want
3// to go". Composes the existing substrate (nx_vizsla_jobs skill profiles + nx_vizsla_recruiter warm-channel
4// routing doctrine + nx_relate recruiter edges + nx_send outreach + nx_crm stage model) and adds the four missing
5// capabilities, integer-deterministic:
6// MATCH-SCORE : candidate skills x job requirements -> 0..100 fit (have>=need = full credit; partial = have/need;
7// missing = 0; mean over requirements). Ranking jobs by fit = "REAL opportunities where they ARE".
8// SKILL-GAP : target role requirements minus profile -> the missing/underleveled list = the concrete plan for
9// "where they want to GO".
10// APP-STAGE : the application pipeline Saved(10)>Applied(25)>Screen(40)>Interview(60)>Offer(85)>Accepted(100)
11// (+Rejected 0) -- the OpportunityStage model applied to job search; funnel dashboard.
12// CAREER-PATH : gap + next-skill recommendation + ROUTE-VIA-RELATIONSHIP (never the cold portal: hand the top
13// match to the strongest recruiter/referral channel, per the nx_vizsla_recruiter doctrine).
14// Pure logic + selftest gate (argless). Persistence rides seg_store next (same canon_cid pattern).
15// Commands: match <have-csv> <need-csv> | gap <have-csv> <target-csv> | stage <name> | selftest
16// skill csv form: name:level[,name:level...] levels 1..5
17// expect_exit: 0 license_tier: ORIGINAL
18import "nx_syscalls.nx"
19
20func slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
21func p(s: *u8) -> i64 { sys_write(1, s, slen(s)); return 0 }
22func pn(v: i64) -> i64 {
23 var m: i64 = v; if m < 0 { p("-" as *u8); m = 0 - m }
24 let t: *u8 = sys_mmap(24); var k: i64 = 0; if m == 0 { t[0] = 48 as u8; k = 1 }
25 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
26 let o: *u8 = sys_mmap(24); var i: i64 = 0; while i < k { o[i] = t[k-1-i]; i = i + 1 } sys_write(1, o, k); return 0
27}
28func seq(a: *u8, b: *u8) -> i64 {
29 var i: i64 = 0
30 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
31 if b[i] != (0 as u8) { return 0 }
32 return 1
33}
34
35// ---- skill csv parser: "sales:4,crm:3" -> parallel arrays (names into namebuf slots of 64) ----
36func skills_parse(csv: *u8, names: *i64, lvls: *i64, maxn: i64, namebuf: *u8) -> i64 {
37 var n: i64 = 0
38 var i: i64 = 0
39 var go: i64 = 1
40 while go == 1 {
41 if n >= maxn { return n }
42 // name until ':'
43 let nm: *u8 = (namebuf as i64 + n * 64) as *u8
44 var t: i64 = 0
45 while csv[i] != (58 as u8) {
46 if csv[i] == (0 as u8) { return n }
47 if t < 63 { nm[t] = csv[i]; t = t + 1 }
48 i = i + 1
49 }
50 nm[t] = 0 as u8
51 i = i + 1
52 // level digits until ',' or NUL
53 var v: i64 = 0
54 while csv[i] != (44 as u8) {
55 if csv[i] == (0 as u8) { break }
56 let c: i64 = csv[i] as i64
57 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } }
58 i = i + 1
59 }
60 names[n] = nm as i64
61 lvls[n] = v
62 n = n + 1
63 if csv[i] == (0 as u8) { go = 0 } else { i = i + 1 }
64 }
65 return n
66}
67func skill_find(names: *i64, n: i64, want: *u8) -> i64 {
68 var i: i64 = 0
69 while i < n { if seq(names[i] as *u8, want) == 1 { return i } i = i + 1 }
70 return 0 - 1
71}
72
73// ---- MATCH-SCORE: 0..100 fit of candidate vs requirements ----
74func match_score(hn: *i64, hl: *i64, hc: i64, rn: *i64, rl: *i64, rc: i64) -> i64 {
75 if rc <= 0 { return 0 }
76 var total: i64 = 0
77 var i: i64 = 0
78 while i < rc {
79 let idx: i64 = skill_find(hn, hc, rn[i] as *u8)
80 if idx >= 0 {
81 let have: i64 = hl[idx]
82 let need: i64 = rl[i]
83 if have >= need { total = total + 100 } else { total = total + (have * 100) / need }
84 }
85 i = i + 1
86 }
87 return total / rc
88}
89
90// ---- SKILL-GAP: what the target needs that the profile lacks (missing or underleveled) ----
91// prints each gap line; returns gap count
92func skill_gap(hn: *i64, hl: *i64, hc: i64, rn: *i64, rl: *i64, rc: i64) -> i64 {
93 var gaps: i64 = 0
94 var i: i64 = 0
95 while i < rc {
96 let idx: i64 = skill_find(hn, hc, rn[i] as *u8)
97 var have: i64 = 0
98 if idx >= 0 { have = hl[idx] }
99 if have < rl[i] {
100 p(" SKILL-GAP " as *u8); p(rn[i] as *u8); p(" have=" as *u8); pn(have); p(" need=" as *u8); pn(rl[i]); p("\n" as *u8)
101 gaps = gaps + 1
102 }
103 i = i + 1
104 }
105 return gaps
106}
107
108// ---- APP-STAGE: the application pipeline ----
109func stage_pct(s: *u8) -> i64 {
110 if seq(s, "Saved" as *u8) == 1 { return 10 }
111 if seq(s, "Applied" as *u8) == 1 { return 25 }
112 if seq(s, "Screen" as *u8) == 1 { return 40 }
113 if seq(s, "Interview" as *u8) == 1 { return 60 }
114 if seq(s, "Offer" as *u8) == 1 { return 85 }
115 if seq(s, "Accepted" as *u8) == 1 { return 100 }
116 if seq(s, "Rejected" as *u8) == 1 { return 0 }
117 return 0 - 1
118}
119func cmd_stage(name: *u8) -> i64 {
120 let v: i64 = stage_pct(name)
121 if v < 0 { p("APP-STAGE ERROR unknown stage=" as *u8); p(name); p(" (Saved|Applied|Screen|Interview|Offer|Accepted|Rejected) -- fail loud\n" as *u8); return 1 }
122 p("APP-STAGE " as *u8); p(name); p(" progress=" as *u8); pn(v); p("%\n" as *u8)
123 return 0
124}
125
126func cmd_selftest() -> i64 {
127 p("=== NX-CAREER SELFTEST (MATCH-SCORE + SKILL-GAP + APP-STAGE + CAREER-PATH) ===\n" as *u8)
128 var ok: i64 = 1
129 let nb: *u8 = sys_mmap(64 * 16)
130 let nb2: *u8 = sys_mmap(64 * 16)
131 let hn: *i64 = sys_mmap(8 * 16) as *i64
132 let hl: *i64 = sys_mmap(8 * 16) as *i64
133 let rn: *i64 = sys_mmap(8 * 16) as *i64
134 let rl: *i64 = sys_mmap(8 * 16) as *i64
135
136 // Emma: where she IS
137 let hc: i64 = skills_parse("sales:4,crm:3,writing:5" as *u8, hn, hl, 16, nb)
138 if hc != 3 { ok = 0 }
139 if hl[0] != 4 { ok = 0 }
140 if seq(hn[2] as *u8, "writing" as *u8) != 1 { ok = 0 }
141
142 // three real openings, scored
143 p("-- Emma vs three openings (MATCH-SCORE = real opportunities where she IS) --\n" as *u8)
144 let c1: i64 = skills_parse("sales:3,crm:3" as *u8, rn, rl, 16, nb2)
145 let s1: i64 = match_score(hn, hl, hc, rn, rl, c1)
146 p(" MATCH-SCORE Account Executive at Westfield = " as *u8); pn(s1); p("/100\n" as *u8)
147 let c2: i64 = skills_parse("sales:5,leadership:4,forecasting:3" as *u8, rn, rl, 16, nb2)
148 let s2: i64 = match_score(hn, hl, hc, rn, rl, c2)
149 p(" MATCH-SCORE Sales Director at Riverside = " as *u8); pn(s2); p("/100\n" as *u8)
150 let c3: i64 = skills_parse("writing:4,seo:2" as *u8, rn, rl, 16, nb2)
151 let s3: i64 = match_score(hn, hl, hc, rn, rl, c3)
152 p(" MATCH-SCORE Content Manager at Downtown = " as *u8); pn(s3); p("/100\n" as *u8)
153 if s1 != 100 { ok = 0 }
154 if s2 != 26 { ok = 0 }
155 if s3 != 50 { ok = 0 }
156
157 // where she WANTS TO GO: Sales Director -> the concrete gap plan
158 p("-- target: Sales Director (SKILL-GAP = where she wants to GO) --\n" as *u8)
159 let c4: i64 = skills_parse("sales:5,leadership:4,forecasting:3" as *u8, rn, rl, 16, nb2)
160 let g: i64 = skill_gap(hn, hl, hc, rn, rl, c4)
161 if g != 3 { ok = 0 }
162 p(" CAREER-PATH next=leadership (largest gap) route=STRONGEST RELATIONSHIP (recruiter/referral via nx_vizsla_recruiter + nx_relate recruits edge), never the cold portal\n" as *u8)
163
164 // application pipeline
165 p("-- application pipeline --\n" as *u8)
166 let a1: i64 = cmd_stage("Saved" as *u8)
167 let a2: i64 = cmd_stage("Applied" as *u8)
168 let a3: i64 = cmd_stage("Interview" as *u8)
169 let abad: i64 = cmd_stage("Ghosted" as *u8)
170 if a1 != 0 { ok = 0 }
171 if a2 != 0 { ok = 0 }
172 if a3 != 0 { ok = 0 }
173 if abad != 1 { ok = 0 }
174 if stage_pct("Offer" as *u8) != 85 { ok = 0 }
175 if stage_pct("Rejected" as *u8) != 0 { ok = 0 }
176
177 p("NX-CAREER-SELFTEST match=(100,26,50) gaps=" as *u8); pn(g); p(" stages=7 " as *u8)
178 if ok == 1 { p("verdict=GREEN\n" as *u8); return 0 }
179 p("verdict=RED\n" as *u8)
180 return 1
181}
182
183// PURE-CORE entry (no main here): consumers = _hdl_build/nx_career_gate (CLI+selftest wrapper),
184// nx_career_parse (posting/resume -> skills), nx_career_fair (the fairness-invariance gate).
185func cr_cli(argc: i64, argv: *i64) -> i64 {
186 if argc < 2 { return cmd_selftest() }
187 let cmd: *u8 = argv[1] as *u8
188 if seq(cmd, "selftest" as *u8) == 1 { return cmd_selftest() }
189 if seq(cmd, "stage" as *u8) == 1 {
190 if argc < 3 { p("usage: stage <name>\n" as *u8); return 1 }
191 return cmd_stage(argv[2] as *u8)
192 }
193 if seq(cmd, "match" as *u8) == 1 {
194 if argc < 4 { p("usage: match <have-csv> <need-csv>\n" as *u8); return 1 }
195 let nb: *u8 = sys_mmap(64 * 16); let nb2: *u8 = sys_mmap(64 * 16)
196 let hn: *i64 = sys_mmap(8 * 16) as *i64; let hl: *i64 = sys_mmap(8 * 16) as *i64
197 let rn: *i64 = sys_mmap(8 * 16) as *i64; let rl: *i64 = sys_mmap(8 * 16) as *i64
198 let hc: i64 = skills_parse(argv[2] as *u8, hn, hl, 16, nb)
199 let rc: i64 = skills_parse(argv[3] as *u8, rn, rl, 16, nb2)
200 let s: i64 = match_score(hn, hl, hc, rn, rl, rc)
201 p("MATCH-SCORE " as *u8); pn(s); p("/100\n" as *u8)
202 return 0
203 }
204 if seq(cmd, "gap" as *u8) == 1 {
205 if argc < 4 { p("usage: gap <have-csv> <target-csv>\n" as *u8); return 1 }
206 let nb: *u8 = sys_mmap(64 * 16); let nb2: *u8 = sys_mmap(64 * 16)
207 let hn: *i64 = sys_mmap(8 * 16) as *i64; let hl: *i64 = sys_mmap(8 * 16) as *i64
208 let rn: *i64 = sys_mmap(8 * 16) as *i64; let rl: *i64 = sys_mmap(8 * 16) as *i64
209 let hc: i64 = skills_parse(argv[2] as *u8, hn, hl, 16, nb)
210 let rc: i64 = skills_parse(argv[3] as *u8, rn, rl, 16, nb2)
211 let g: i64 = skill_gap(hn, hl, hc, rn, rl, rc)
212 p("SKILL-GAP total=" as *u8); pn(g); p("\n" as *u8)
213 return 0
214 }
215 p("NX-CAREER usage: match | gap | stage | selftest\n" as *u8)
216 return 1
217}