code wiki / (root) / nx_crm.nx

nx_crm.nx source

↩ module page · 200 lines · 11083 B

1// nx_crm.nx -- PROFESSIONAL CRM PIPELINE (rung 2 of Nishi CRM/Relationships to SOTA). Operator 2026-07-08: 2// "clients like andelinwest.com can use a state of the art crm". The sales/deal layer on top of the nx_relate 3// entity graph: an ACCOUNT is a company entity you do business with; an OPPORTUNITY (deal) has an amount, a close 4// date, and a PIPELINE-STAGE; each stage carries a win PROBABILITY + a forecast category, so the DASHBOARD shows a 5// weighted forecast (Salesforce OpportunityStage model, verified 2026-07-08). Plus ACTIVITY (tasks/calls/meetings on 6// a record) + SEQUENCE (a follow-up cadence that auto-stops on reply). Composes with nx_relate (accounts + contacts 7// are entities) + nx_send (reach any contact) + nx_vizsla (timeline/reminders). 8// NOTE (research reframe): long-term these become object/edge DEFINITIONS on the flexible nx_relate engine, not a 9// hardcoded silo -- this focused organ ships the value now + is refactorable to engine-config (rework-not-retire). 10// Pure logic + a self-test gate (argless = selftest). Persistence = next rung. 11// Commands: stages | account <tenant> <name> | opp <tenant> <name> <account> <amount> <stage> | activity <tenant> <record> <type> <subject> | sequence <name> <steps> | dashboard | selftest 12// expect_exit: 0 license_tier: ORIGINAL 13import "nx_syscalls.nx" 14const K_MAGIC_50000: i64 = 50000 15const K_MAGIC_120000: i64 = 120000 16const K_MAGIC_30000: i64 = 30000 17const K_MAGIC_20000: i64 = 20000 18const K_MAGIC_10000: i64 = 10000 19const K_MAGIC_122500: i64 = 122500 20 21func slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 22func p(s: *u8) -> i64 { sys_write(1, s, slen(s)); return 0 } 23func pn(v: i64) -> i64 { 24 var m: i64 = v; if m < 0 { p("-" as *u8); m = 0 - m } 25 let t: *u8 = sys_mmap(24); var k: i64 = 0; if m == 0 { t[0] = 48 as u8; k = 1 } 26 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 27 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 28} 29func seq(a: *u8, b: *u8) -> i64 { 30 var i: i64 = 0 31 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 32 if b[i] != (0 as u8) { return 0 } 33 return 1 34} 35 36// ---- PIPELINE-STAGE: name -> win probability (percent) + forecast category ---- 37func stage_valid(s: *u8) -> i64 { 38 if seq(s, "Lead" as *u8) == 1 { return 1 } 39 if seq(s, "Qualified" as *u8) == 1 { return 1 } 40 if seq(s, "Proposal" as *u8) == 1 { return 1 } 41 if seq(s, "Negotiation" as *u8) == 1 { return 1 } 42 if seq(s, "ClosedWon" as *u8) == 1 { return 1 } 43 if seq(s, "ClosedLost" as *u8) == 1 { return 1 } 44 return 0 45} 46func stage_prob(s: *u8) -> i64 { 47 if seq(s, "Lead" as *u8) == 1 { return 10 } 48 if seq(s, "Qualified" as *u8) == 1 { return 25 } 49 if seq(s, "Proposal" as *u8) == 1 { return 50 } 50 if seq(s, "Negotiation" as *u8) == 1 { return 75 } 51 if seq(s, "ClosedWon" as *u8) == 1 { return 100 } 52 if seq(s, "ClosedLost" as *u8) == 1 { return 0 } 53 return 0 - 1 54} 55func stage_forecast(s: *u8) -> *u8 { 56 if seq(s, "Lead" as *u8) == 1 { return "Pipeline" as *u8 } 57 if seq(s, "Qualified" as *u8) == 1 { return "Pipeline" as *u8 } 58 if seq(s, "Proposal" as *u8) == 1 { return "BestCase" as *u8 } 59 if seq(s, "Negotiation" as *u8) == 1 { return "Commit" as *u8 } 60 if seq(s, "ClosedWon" as *u8) == 1 { return "Closed" as *u8 } 61 if seq(s, "ClosedLost" as *u8) == 1 { return "Omitted" as *u8 } 62 return "?" as *u8 63} 64func stage_open(s: *u8) -> i64 { 65 if seq(s, "ClosedWon" as *u8) == 1 { return 0 } 66 if seq(s, "ClosedLost" as *u8) == 1 { return 0 } 67 return 1 68} 69 70func cmd_stages() -> i64 { 71 p("=== NX-CRM PIPELINE-STAGE ladder (stage -> probability -> forecast) ===\n" as *u8) 72 let st: *i64 = sys_mmap(8 * 8) as *i64 73 st[0]="Lead" as *u8 as i64; st[1]="Qualified" as *u8 as i64; st[2]="Proposal" as *u8 as i64; st[3]="Negotiation" as *u8 as i64; st[4]="ClosedWon" as *u8 as i64; st[5]="ClosedLost" as *u8 as i64 74 var i: i64 = 0 75 while i < 6 { 76 let s: *u8 = st[i] as *u8 77 p(" " as *u8); p(s); p(" prob=" as *u8); pn(stage_prob(s)); p("% forecast=" as *u8); p(stage_forecast(s)); p("\n" as *u8) 78 i = i + 1 79 } 80 return 0 81} 82func cmd_account(tenant: *u8, name: *u8) -> i64 { 83 p("ACCOUNT tenant=" as *u8); p(tenant); p(" name=" as *u8); p(name); p(" (= a company entity in nx_relate)\n" as *u8); return 0 84} 85func cmd_opp(tenant: *u8, name: *u8, account: *u8, amount: i64, stage: *u8) -> i64 { 86 if stage_valid(stage) == 0 { p("OPPORTUNITY ERROR unknown stage=" as *u8); p(stage); p(" -- fail loud\n" as *u8); return 1 } 87 if amount < 0 { p("OPPORTUNITY ERROR negative amount -- fail loud\n" as *u8); return 1 } 88 let w: i64 = amount * stage_prob(stage) / 100 89 p("OPPORTUNITY tenant=" as *u8); p(tenant); p(" deal=" as *u8); p(name); p(" account=" as *u8); p(account) 90 p(" amount=" as *u8); pn(amount); p(" PIPELINE-STAGE=" as *u8); p(stage); p(" prob=" as *u8); pn(stage_prob(stage)) 91 p("% weighted=" as *u8); pn(w); p(" forecast=" as *u8); p(stage_forecast(stage)); p("\n" as *u8) 92 return 0 93} 94func cmd_activity(tenant: *u8, record: *u8, kind: *u8, subject: *u8) -> i64 { 95 var ok: i64 = 0 96 if seq(kind, "call" as *u8) == 1 { ok = 1 } 97 if seq(kind, "email" as *u8) == 1 { ok = 1 } 98 if seq(kind, "meeting" as *u8) == 1 { ok = 1 } 99 if seq(kind, "task" as *u8) == 1 { ok = 1 } 100 if ok == 0 { p("ACTIVITY ERROR unknown type=" as *u8); p(kind); p(" (call|email|meeting|task) -- fail loud\n" as *u8); return 1 } 101 p("ACTIVITY tenant=" as *u8); p(tenant); p(" on=" as *u8); p(record); p(" type=" as *u8); p(kind); p(" subject=" as *u8); p(subject); p("\n" as *u8) 102 return 0 103} 104func cmd_sequence(name: *u8, steps: i64) -> i64 { 105 if steps < 1 { p("SEQUENCE ERROR needs >=1 step -- fail loud\n" as *u8); return 1 } 106 p("SEQUENCE name=" as *u8); p(name); p(" steps=" as *u8); pn(steps); p(" auto-stop-on-reply=yes (channels: email/call/postcard via nx_send)\n" as *u8) 107 return 0 108} 109 110// DASHBOARD: weighted forecast over a demo pipeline (persistence is the next rung). 111// returns the weighted OPEN forecast (the gate checks it). 112func cmd_dashboard() -> i64 { 113 p("=== NX-CRM DASHBOARD -- Andelin West pipeline (weighted forecast) ===\n" as *u8) 114 let nm: *i64 = sys_mmap(8 * 8) as *i64 115 let am: *i64 = sys_mmap(8 * 8) as *i64 116 let sg: *i64 = sys_mmap(8 * 8) as *i64 117 nm[0]="Westfield Renovation" as *u8 as i64; am[0]=K_MAGIC_50000; sg[0]="Proposal" as *u8 as i64 118 nm[1]="Riverside Contract" as *u8 as i64; am[1]=K_MAGIC_120000; sg[1]="Negotiation" as *u8 as i64 119 nm[2]="Downtown Lease" as *u8 as i64; am[2]=K_MAGIC_30000; sg[2]="Qualified" as *u8 as i64 120 nm[3]="Old Bid" as *u8 as i64; am[3]=K_MAGIC_20000; sg[3]="ClosedLost" as *u8 as i64 121 var open_total: i64 = 0 122 var weighted: i64 = 0 123 var won: i64 = 0 124 var i: i64 = 0 125 while i < 4 { 126 let s: *u8 = sg[i] as *u8 127 let a: i64 = am[i] 128 p(" " as *u8); p(nm[i] as *u8); p(" $" as *u8); pn(a); p(" " as *u8); p(s); p(" (" as *u8); pn(stage_prob(s)); p("%)\n" as *u8) 129 if stage_open(s) == 1 { open_total = open_total + a; weighted = weighted + (a * stage_prob(s) / 100) } 130 if seq(s, "ClosedWon" as *u8) == 1 { won = won + a } 131 i = i + 1 132 } 133 p(" ----\n open pipeline = $" as *u8); pn(open_total); p(" weighted forecast = $" as *u8); pn(weighted); p(" won = $" as *u8); pn(won); p("\n" as *u8) 134 return weighted 135} 136 137func cmd_selftest() -> i64 { 138 p("=== NX-CRM SELFTEST (ACCOUNT + OPPORTUNITY + PIPELINE-STAGE + ACTIVITY + SEQUENCE + DASHBOARD) ===\n" as *u8) 139 var ok: i64 = 1 140 // stage validity + probability (pos + neg) 141 if stage_valid("Proposal" as *u8) != 1 { ok = 0 } 142 if stage_valid("ClosedWon" as *u8) != 1 { ok = 0 } 143 if stage_valid("Nonsense" as *u8) != 0 { ok = 0 } 144 if stage_prob("Negotiation" as *u8) != 75 { ok = 0 } 145 if stage_prob("ClosedLost" as *u8) != 0 { ok = 0 } 146 if stage_prob("Lead" as *u8) != 10 { ok = 0 } 147 if stage_open("Proposal" as *u8) != 1 { ok = 0 } 148 if stage_open("ClosedWon" as *u8) != 0 { ok = 0 } 149 // commands 150 let a1: i64 = cmd_account("andelinwest" as *u8, "Westfield Group" as *u8) 151 let o1: i64 = cmd_opp("andelinwest" as *u8, "Westfield Renovation" as *u8, "Westfield Group" as *u8, K_MAGIC_50000, "Proposal" as *u8) 152 let obad: i64 = cmd_opp("andelinwest" as *u8, "Bad Deal" as *u8, "X" as *u8, K_MAGIC_10000, "Nonsense" as *u8) 153 let ac1: i64 = cmd_activity("andelinwest" as *u8, "Westfield Renovation" as *u8, "call" as *u8, "Discuss scope" as *u8) 154 let acbad: i64 = cmd_activity("andelinwest" as *u8, "X" as *u8, "smoke-signal" as *u8, "nope" as *u8) 155 let sq1: i64 = cmd_sequence("New lead nurture" as *u8, 4) 156 if a1 != 0 { ok = 0 } 157 if o1 != 0 { ok = 0 } 158 if obad != 1 { ok = 0 } 159 if ac1 != 0 { ok = 0 } 160 if acbad != 1 { ok = 0 } 161 if sq1 != 0 { ok = 0 } 162 // dashboard weighted forecast must equal the known demo total 163 let wf: i64 = cmd_dashboard() 164 if wf != K_MAGIC_122500 { ok = 0 } 165 p("NX-CRM-SELFTEST stages=6 weighted-forecast=" as *u8); pn(wf); p(" " as *u8) 166 if ok == 1 { p("verdict=GREEN\n" as *u8); return 0 } 167 p("verdict=RED\n" as *u8) 168 return 1 169} 170 171// PURE-CORE entry (no main): consumers = _hdl_build/nx_crm_gate (CLI+selftest wrapper) + nx_crm_store (persistence). 172func cm_cli(argc: i64, argv: *i64) -> i64 { 173 if argc < 2 { return cmd_selftest() } 174 let cmd: *u8 = argv[1] as *u8 175 if seq(cmd, "selftest" as *u8) == 1 { return cmd_selftest() } 176 if seq(cmd, "stages" as *u8) == 1 { return cmd_stages() } 177 if seq(cmd, "dashboard" as *u8) == 1 { let w: i64 = cmd_dashboard(); return 0 } 178 if seq(cmd, "account" as *u8) == 1 { 179 if argc < 4 { p("usage: account <tenant> <name>\n" as *u8); return 1 } 180 return cmd_account(argv[2] as *u8, argv[3] as *u8) 181 } 182 if seq(cmd, "opp" as *u8) == 1 { 183 if argc < 7 { p("usage: opp <tenant> <name> <account> <amount> <stage>\n" as *u8); return 1 } 184 var amt: i64 = 0; var i: i64 = 0; let a: *u8 = argv[5] as *u8 185 while a[i] != (0 as u8) { let d: i64 = (a[i] as i64) - 48; if d >= 0 { if d <= 9 { amt = amt * 10 + d } } i = i + 1 } 186 return cmd_opp(argv[2] as *u8, argv[3] as *u8, argv[4] as *u8, amt, argv[6] as *u8) 187 } 188 if seq(cmd, "activity" as *u8) == 1 { 189 if argc < 6 { p("usage: activity <tenant> <record> <type> <subject>\n" as *u8); return 1 } 190 return cmd_activity(argv[2] as *u8, argv[3] as *u8, argv[4] as *u8, argv[5] as *u8) 191 } 192 if seq(cmd, "sequence" as *u8) == 1 { 193 if argc < 4 { p("usage: sequence <name> <steps>\n" as *u8); return 1 } 194 var stp: i64 = 0; var i: i64 = 0; let a: *u8 = argv[3] as *u8 195 while a[i] != (0 as u8) { let d: i64 = (a[i] as i64) - 48; if d >= 0 { if d <= 9 { stp = stp * 10 + d } } i = i + 1 } 196 return cmd_sequence(argv[2] as *u8, stp) 197 } 198 p("NX-CRM usage: stages | account | opp | activity | sequence | dashboard | selftest\n" as *u8) 199 return 1 200}